{"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":"e3e79c5a-d0ec-4f4f-975c-9ddf3ee1d18b","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"76c79dbd9ee234f8a00671100d8f54fe8f125785437d86da9d7e23fd1db666a9","dependsOn":["audit_imported_code"],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"46f40e7ec25c006217e1034c5e7b4f8450e2d634634141d94b53e3820c9eea61","dependsOn":["adapt_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":"2b1756ea0c9cad1bd75b945edf5626b0b733e1564b4964545045878c1dec941b","dependsOn":["adapt_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":"92cf57b6213a488a639267dc5f9bfe54232251dec6e0cd43b23cb6dbec1d629e","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_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":"failed"},{"acceptedSubmissionHash":"c79071ff9cc6639d50680b2d84938d1dee4c553c8326e0583f4b845746c658b1","dependsOn":["adapt_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":"cf026dd6b00537befec88a3aa0490f59fa927398e0f002c4a6502c2bdeb4ed65","dependsOn":["adapt_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":"4b406fccb6d5bdca1bb96f48fb197b07e0a835f551983667fc760fb7615696e9","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"deda0a2372754ca88e3962317005225a5f5e71d846b1b40c0ef0823153b07f44","dependsOn":["adapt_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":"PROJECT: BMC ILLUMINATI PASS #1356 — CHAINLINK VRF SECURITY FIX\n\nRepository:\nhttps://github.com/0xSt3w/launch-1356-project-bmc-illuminati-pass\n\nReference security audit:\nhttps://github.com/Identity-md/research/blob/main/jobs/8e2c8de5-58ed-4243-bd09-bfa32e0eafde/files/AUDIT.md\n\nTASK:\n\nFix the MEDIUM severity finding concerning expired Chainlink VRF v2.5 requests permanently blocking mystery NFT reveals.\n\nFirst independently verify the Chainlink request-expiration behavior and assess the security implications of the proposed remedies.\n\nDesign and implement a safe recovery mechanism that preserves the fairness of the NFT draw and prevents owner-controlled rerolls, selective retries or manipulation of rarity outcomes.\n\nPreserve:\n- Maximum supply: 500 NFTs\n- 40 sequential Genesis mints\n- 460 Mystery mints\n- 77 total Genesis outcomes\n- 423 Standard outcomes\n- Legendary Genesis #1\n- Genesis mint price: 1,000,000 BMC\n- Mystery mint price: 350,000 BMC\n- Permanent BMC burning\n- Chainlink VRF randomness\n\nREQUIREMENTS:\n\n1. Create a separate security-fix branch and pull request.\n2. Document the recovery design and its trust assumptions before implementing it.\n3. Add tests for expired or missing VRF requests, out-of-order fulfillment, duplicate callbacks and attempted rarity manipulation.\n4. Preserve existing metadata and supply invariants.\n5. Run all existing Foundry tests and new security tests.\n6. Report any changes to the randomness and fairness guarantees.\n7. Provide an independent security re-review of the proposed changes.\n\nDO NOT deploy contracts.\nDO NOT broadcast transactions.\nDO NOT request wallet approvals.\nDO NOT merge the pull request.\nDO NOT modify staking economics.\nDO NOT publish a new website.\n\nPerform a free feasibility check and disclose any costs before paid work. Wait for explicit approval before payment or deployment.","parentJobId":null,"planHash":"61c3bf06b212e96e49725eeadd09b3fab0c1f6eb1a9d3c297554faf79589419e","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e3e79c5a-d0ec-4f4f-975c-9ddf3ee1d18b","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51315","feedbackHash":"7d21f1f492a9d9d3d67acff7d7498ec3cf505a80220a4c88ba8ac80f167159d9","nodeKey":"adapt_contract_project","submissionHash":"76c79dbd9ee234f8a00671100d8f54fe8f125785437d86da9d7e23fd1db666a9","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51027","feedbackHash":"d6c2eec2fd0880ec85d2c70d7d2f189195dcef73142e63b1b25f4ce39fdc7026","nodeKey":"audit_economics","submissionHash":"46f40e7ec25c006217e1034c5e7b4f8450e2d634634141d94b53e3820c9eea61","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52391","feedbackHash":"019c76c7f4207749c9864f358b71fad7f2f24215e186a56ca668564e1d4f6d97","nodeKey":"audit_flow","submissionHash":"2b1756ea0c9cad1bd75b945edf5626b0b733e1564b4964545045878c1dec941b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51163","feedbackHash":"f187754261c135b1262bede8f7939bafb2a3f870f498bc25a81c8c41a96f16a6","nodeKey":"audit_imported_code","submissionHash":"92cf57b6213a488a639267dc5f9bfe54232251dec6e0cd43b23cb6dbec1d629e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51291","feedbackHash":"4095c484af3300920c6a735ae598d96851a20e9881d1e4f85b1b1486a9c415a9","nodeKey":"audit_math","submissionHash":"c79071ff9cc6639d50680b2d84938d1dee4c553c8326e0583f4b845746c658b1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52679","feedbackHash":"ed92dcac65bf6eb9d2714bfb41a91d813b419ffc82fdbeb687babaf8d212c8d1","nodeKey":"audit_permissions","submissionHash":"cf026dd6b00537befec88a3aa0490f59fa927398e0f002c4a6502c2bdeb4ed65","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51515","feedbackHash":"9ff10383ddbe98a0b49c8b7c7d14068447bafbc411d4958a6f9392d750e7d97f","nodeKey":"manifest","submissionHash":"4b406fccb6d5bdca1bb96f48fb197b07e0a835f551983667fc760fb7615696e9","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52482","feedbackHash":"d6f411c0f4f84d4c2e9d45c463cd2ffcda5204dc1d2b1e0659320b3228e396e6","nodeKey":"write_foundry_tests","submissionHash":"deda0a2372754ca88e3962317005225a5f5e71d846b1b40c0ef0823153b07f44","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"a1f64bc82e4a2b20b7db2f7543af4d8d7ac8dd533cae37b03849f47df7b76328","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"743b05d8a235188d","findings":[{"citation":"resolved","description":"The only validation on keyHash is != 0. Chainlink's real VRFCoordinatorV2_5.requestRandomWords (contracts v1.4.0, the version vendored in lib/) deliberately does not check that the key hash is a registered proving key (source comment: skipped 'to save gas'), so a request on an unknown or retired lane is accepted, a commitment is stored, the consumer nonce and pendingReqCount are incremented, and no node ever fulfils it. Consequences on this contract's control flow: (1) mintMystery does not revert on a bad lane: _burnExact destroys 350,000 BMC, the token is minted AwaitingRandomness and stays there; (2) rerequestRandomness after 72h re-opens a request with the same dead config, so the permissionless recovery cannot recover from this cause until the owner notices and calls setVRFConfig again; (3) every dropped request permanently raises pendingReqCount on the coordinator, so the subscription can never be cancelled nor the pass removed as a consumer (the README mentions only cancelSubscription). README line 113 ('A bad config only blocks new mystery mints until corrected') and NatSpec lines 29-31 present a misconfiguration as fail-closed; it is fail-open for keyHash (and for a funded-but-wrong subscription). Key hashes rotate when Chainlink retires lanes, so this is a realistic operational state, not only a typo. If ownership has been renounced the tokens are stuck forever. Minimal fix: in setVRFConfig query the coordinator's public s_provingKeys(bytes32) (returns (bool exists, uint64 maxGas) on VRFCoordinatorV2_5) through a two-function interface and revert InvalidVRFConfig when !exists (this runs post-launch, so it does not break the empty-chain constructor rule); optionally also require callbackGasLimit <= maxGas. Correct the README/NatSpec sentence either way.","line":207,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: 40 Genesis minted, pass added as consumer of a funded subscription on VRFCoordinatorV2_5Mock. Owner calls setVRFConfig(subId, keccak256('typo'), 500000, 3, false) -> succeeds. Alice approves 350_000e18 BMC and calls mintMystery() -> succeeds, bmc.balanceOf(alice)==0, revealStatusOf(41)==AwaitingRandomness, tokenIdOfRequest(req)==41 and the coordinator holds the request under the dead lane. Expected per README line 113: the mint is blocked until the config is corrected. Actual: BMC burned, token pending indefinitely. warp(+72h); rerequestRandomness(41) -> succeeds and opens another request with keyHash==keccak256('typo') (vrfConfig() still returns it), so the recovery path loops on the dead lane. Scratch test test/scratch/BadLane.t.sol::test_TypoKeyHashAcceptedMintBurnsAndStalls executes exactly this sequence and passes on the current code (it demonstrates the behaviour; it is not a failing proof because severity is low).","severity":"low","snippet":"            subscriptionId == 0 || keyHash == bytes32(0) || callbackGasLimit < MIN_CALLBACK_GAS_LIMIT","title":"setVRFConfig accepts a key hash no VRF node serves; mystery mints then burn BMC into requests that can never be fulfilled, and the README's claim that a bad config 'only blocks new mystery mints' is f"},{"citation":"resolved","description":"Independent verification of the premise the fix rests on: docs.chain.link/vrf/v2-5/overview/subscription says 'If your balance is below the minimum, your requests remain pending for up to 24 hours before they expire', and the node job documentation (docs.chain.link/chainlink-nodes/oracle-jobs/all-jobs, VRF job type) defines requestTimeout as 'The timeout for a request. Defaults to 24 hours.' There is no on-chain expiry in VRFCoordinatorV2_5: the commitment stays valid forever and fulfillRandomWords has no age check. So the 'never served after 24h' property is the default configuration of each node operator on the chosen lane, not a guarantee. The contract's fairness argument in docs/VRF-RECOVERY-DESIGN.md section 4 ('No discard of an in-flight word') and README lines 92-93 ('there is never a word in flight that a re-request could discard') hold only while every node serving the configured key hash keeps requestTimeout below 72h. If an operator ran a longer timeout, a holder watching the mempool could front-run a late fulfilment carrying an unfavourable word with rerequestRandomness and obtain a fresh draw; the contract has no on-chain defence against that beyond the timeout constant. The implementer lists Chainlink expiry as trust assumption 1 but characterises it as 'documented as 24h'; it should be stated as 'node default 24h, not enforced on-chain', and the owner's runbook should include confirming the lane's node configuration (or choosing a larger margin) before enabling mystery minting. No code change is required for the stated design; this is a correction to the stated guarantee the brief asks to be reported (requirement 6).","line":104,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: token 41 minted at T0 with request ra; no delivery. At T0 + 72h a fulfilment for ra carrying word W is pending in the mempool (only possible if a node served a 72h-old request). Attacker (the holder) computes keccak256(abi.encode(W, 41)) % 460 >= 37 (Standard) and submits rerequestRandomness(41) first: passes the AwaitingRandomness check and block.timestamp >= requestedAtOf + 72h, deletes tokenIdOfRequest[ra], opens rb. The fulfilment for ra then reverts UnknownRequest(ra) (coordinator records success=false, charges the subscription) and the token gets a new word from rb. Expected per design doc: 'A front-runner has nothing to front-run'. Actual: true only under the node's default timeout; the contract itself allows the discard at exactly T0+72h (test_FrontRunningFulfilmentBeforeTimeoutFails stops at 71h and does not cover this boundary).","severity":"info","snippet":"    uint256 public constant VRF_REREQUEST_TIMEOUT = 72 hours;","title":"The 72h re-request gate relies on a per-node job-spec default (requestTimeout = 24h), not on a protocol or coordinator rule; the design documents describe it as Chainlink's fixed behaviour"}],"hash":"2b1756ea0c9cad1bd75b945edf5626b0b733e1564b4964545045878c1dec941b","nodeId":"c9d6e1be-8a6b-4ff3-add1-7459145390ee","outcome":"completed","summary":"The findings file is valid, both cited snippets match the tree exactly, and the working tree shows no tracked changes (only the git-ignored scratch directory and the findings file). The review is complete.\n\n**Outcome:** the VRF recovery fix holds up under the Execution Trace, Periphery and First Principles guides. I found one low-severity defect and one documentation-level correction to the stated fairness guarantee. No critical or high findings, so no failing proof tests were required.\n\n**What I verified independently**\n\n- Chainlink's subscription docs confirm the 24-hour expiry statement the fix relies on. The node job-spec docs show this is a per-node `requestTimeout` default, and the real v2.5 coordinator has no on-chain expiry at all.\n- The real coordinator source confirms a dropped request permanently blocks both `cancelSubscription` and `removeConsumer`, and that `requestRandomWords` deliberately skips key-hash validation.\n- All 64 existing tests pass. Scratch gas sweeps from 400k to 700k callback gas never produced a failed inline batch, so the gas-budget lead from static analysis is dead.\n\n**Findings written to `.imd-findings.json`**\n\n1. **Low.** `setVRFConfig` cannot detect an unserviceable key hash, and the real coordinator accepts any value. A mistyped or retired lane means every mystery mint burns 350k BMC into a request no node will ever serve, and the 72-hour re-request reopens the same dead lane. The README states a bad config only blocks mints, which is false. Reproduced against Chainlink's own coordinator mock in `test/scratch/BadLane.t.sol`. Suggested fix is to query the coordinator's public proving-key mapping in the setter.\n2. **Info.** The 72-hour margin is sound only while node operators keep the default 24-hour timeout. The design doc presents this as documented Chainlink behaviour rather than a node configuration default. No code change needed, but the stated guarantee and the owner runbook should say so.\n\n**Coverage:** all 17 listed entry points have a row, plus three invariant rows and the rejected gas lead. Everything traced holds except `setVRFConfig`, which carries finding 1.","treeHash":null,"usage":{"cachedInputTokens":1996038,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":51812,"runtime":"claude","turns":45,"wallClockMs":752187}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"61b4050710026370","findings":[{"citation":"resolved","description":"The recovery design (docs/VRF-RECOVERY-DESIGN.md section 4, README 'Unavailable randomness and recovery') rests on the claim that at the moment a re-request becomes possible there is 'no randomness in flight to discard' and that 'nobody ever gets to choose between two words'. That holds only if the abandoned request's word is unknown. It is not unknown in the very failure mode the recovery exists for. On the real VRFCoordinatorV2_5 (mainnet 0xD7f86b4b8Cae7D942340FF628F82735b7a20893a, source checked at chainlink v2.17.0 contracts/src/v0.8/vrf/dev/VRFCoordinatorV2_5.sol): (1) requestRandomWords has no balance check, so an under-funded subscription still gets a request and commitment; (2) fulfillRandomWords(Proof, RequestCommitmentV2Plus, bool) is external with only nonReentrant, so anyone holding a valid proof can submit it; (3) the random words are a pure function of the proof calldata (randomness = VRF._randomValueFromVRFProof(proof, keccak(proof.seed, blockhash)); randomWords[i] = keccak256(abi.encode(randomness, i))); (4) payment is charged after the callback in the same transaction and an InsufficientBalance revert reverts the whole transaction, including 'delete s_requestCommitments', so the commitment stays live. Therefore a node fulfilment transaction that lands on-chain and reverts for insufficient balance (the under-funded / gas-spike case the README's daily-funding duty is written for) publishes the proof and hence the exact random word of that request, while the request remains fulfillable by anyone who re-submits the same calldata once the subscription is funded (anyone can fund). The pass contract keeps accepting that old request id for as long as it is mapped (rawFulfillRandomWords line 320-327; test_RerequestRevertsCleanlyWhenCoordinatorRejects explicitly asserts 'a late delivery for the still-live id is accepted'), and at requestedAt + 72h it lets anyone unmap it via rerequestRandomness (lines 273-275). Two permissionless actions with opposite effects on the same known word are therefore open at once: resubmit the proof (lock in the known word, always possible, nobody can block it before 72h) or re-request (discard it). The holder of the pending token computes the outcome in advance (keccak256(abi.encode(word, tokenId)) % remaining against the public pool state, line 360) and takes whichever branch is better; a competitor with the opposite interest can race only on the discard branch. The owner, if it is also the subscription owner, has the same lever plus control over when the subscription is funded. Economics: Genesis odds for a single draw are 37/460 = 8.0%; a holder who can lock in a known Genesis word and otherwise redraw raises that to about 1-(1-0.08)^2 = 15.4% per leaked request, at the cost of one VRF fulfilment fee (a few dollars of LINK/ETH) and gas, against a Genesis/Standard value gap the project itself prices at >= 650,000 BMC (1,000,000 BMC Genesis mint vs 350,000 BMC mystery mint). Secondary effect: with the leaked proof, anyone can also resubmit the old request after a re-request, making the subscription pay for a failed callback (the pass reverts UnknownRequest; the coordinator still charges). The contract cannot detect a leaked proof, so the fix is partly outside the code: (a) state this precondition honestly in docs/VRF-RECOVERY-DESIGN.md section 4/5 and the README instead of claiming no choice can exist; (b) require the operator/keeper to re-submit any reverted fulfilment's proof as soon as the subscription is funded (while the old id is still mapped) so the known word is always used, and keep the subscription funded with a margin above the lane's max gas price so fulfilments do not revert; (c) optionally, to remove the holder's guaranteed 'lock in a good word' branch, finalize in the callback only when the token is at the head of the queue is already the case; what remains is the operational rule in (b).","line":274,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: Genesis phase done (40 mints). Alice mints mystery token 41 at T0 -> request R1 (AwaitingRandomness). The subscription is under-funded; the Chainlink node's fulfilRandomWords(proof,rc) for R1 lands and reverts InsufficientBalance (or is otherwise visible), so word W1 for R1 is public; the node stops retrying after 24h, the commitment for R1 stays. Alice computes r = keccak256(abi.encode(W1, 41)) % 460 and sees r >= 37 (Standard). At T0 + 72h both of these succeed from the same state: Path A (anyone): fund subscription, resubmit fulfillRandomWords(proof,rc) for R1 -> pass.rawFulfillRandomWords(R1,[W1]) stores W1, token 41 = Standard. Path B (Alice): pass.rerequestRandomness(41) -> tokenIdOfRequest[R1] deleted, new request R2; a later delivery of R1 is now rejected (UnknownRequest), R2's fresh word W2 decides the token (8% Genesis). Alice takes B because A is known-bad; had W1 been Genesis she would have taken A before 72h, when nobody can stop it. Expected per docs: at most one of these paths is ever available ('no choice between two words'). Actual: both are, selectable by the holder. Scratch test run on this tree (test/scratch/LeakedProofChoice.t.sol, MockVRFCoordinator standing in for proof resubmission): from a snapshot at T0+72h, vrf.fulfill(oldReq, standardWord) -> tierOf(41)==Standard; revert to snapshot; alice.rerequestRandomness(41); vrf.fulfill(oldReq, standardWord) returns false; vrf.fulfill(newReq, genesisWord) -> tierOf(41)==Genesis. Both branches executed; the test's final revert('both paths open') fired.","severity":"medium","snippet":"        uint256 oldRequestId = requestIdOf[tokenId];\n        delete tokenIdOfRequest[oldRequestId];\n        newRequestId = _openRequest(tokenId, vrfConfig);","title":"Re-request can discard a VRF word that is already public: a holder (or anyone) chooses between the known old word and a fresh one, breaking the 'nobody picks between two deliveries' guarantee"}],"hash":"46f40e7ec25c006217e1034c5e7b4f8450e2d634634141d94b53e3820c9eea61","nodeId":"73cebde0-5c1e-461f-ae8a-5a35e1c03330","outcome":"completed","summary":"Review complete. The findings file is written and validated, the scratch test is removed, and the working tree holds no other changes.\n\n**Outcome.** One substantiated finding, medium severity, in the assigned area (economic security, invariants, flow gaps). Everything else I traced holds, and all 17 listed entry points have coverage rows plus three invariant rows.\n\n**The finding.** The recovery design claims nobody can ever choose between two VRF words for a token. That guarantee depends on the abandoned request's word being unknown, which fails in the exact scenario the recovery targets. On the real Chainlink v2.5 coordinator, a fulfilment that reverts for insufficient subscription balance publishes the proof in calldata while leaving the request commitment live, and anyone can later resubmit that proof. So at the 72-hour mark the pending token's holder has two open paths: resubmit the known word, or call `rerequestRandomness` to discard it. They take whichever is better, roughly doubling their Genesis odds per leaked request. I reproduced both branches from one state with the project's mock coordinator and anchored the finding at `src/BMCIlluminatiPass.sol:274`. The contract cannot detect a leaked proof, so the recommended fix is to state the precondition honestly in the design doc and README, and to add an operational rule that any reverted fulfilment's proof is resubmitted promptly while the old id is still mapped.\n\n**What held.** Mint pricing and exact-burn verification, the 500/40/460/77/423 supply and artwork invariants with re-requests in the loop, strict mint-order finalisation, the Fisher-Yates draw, callback idempotence, owner powers being delay-only, re-request griefing bounds, and the plain launch token.\n\n**Not covered.** I did not re-derive the gas-per-reveal measurements or the IPFS pin verification, and I could not exercise the live BMC token or a mainnet fork from this environment.","treeHash":null,"usage":{"cachedInputTokens":861671,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":37817,"runtime":"claude","turns":34,"wallClockMs":584097}},{"artifacts":[],"attempt":1,"bundleHash":"375e16d3f8c30b015c3a7b077759ca356db44c871a720082221d071a5927a22d","device":"ea89e16822824c6f","findings":[],"hash":"4b406fccb6d5bdca1bb96f48fb197b07e0a835f551983667fc760fb7615696e9","nodeId":"778654eb-fe57-4ee6-bd41-71f30588aa88","outcome":"completed","summary":"`launch.json` is written and validated. No other tracked file was changed.\n\n**What the manifest contains**\n\n- Token: LaunchToken, \"Illuminati Pass\" / IPASS, 18 decimals, as `src/LaunchToken.sol` sets them.\n- One application contract, BMCIlluminatiPass, with constructor arguments in declaration order: `$owner` for initialOwner, the mainnet BMC token address for bmcToken, and the mainnet Chainlink VRF v2.5 coordinator for vrfCoordinator. Both addresses are lowercase and match the deploy script, README, tests and NatSpec in the accepted tree.\n- Pool exactly as the launch section gives it: native ETH, fee 12500, tick spacing 60, the one-to-one initial price.\n- Notes under 4,000 characters describing the pass, the owner's only power (setVRFConfig, set after launch), the permissionless 72-hour re-request recovery, and that no contract receives launch token supply.\n\nStandardArt is an internal library inlined into the pass, so it is not a deployable contract and is not listed.\n\n**Checks run**\n\n- `forge build` clean, `forge test` 64 passed, 0 failed, 1 skipped (the mainnet fork test without a fork).\n- Schema check in Python: only the five top-level keys, unique names within 32 characters, address strings lowercase, pool values verbatim.\n- The pinned floor tests run from test/scratch against the real creation code with a CREATE2 stand-in factory, chain id 1, the launch owner as `$owner`, and the policy supply: 9 passed. The pass constructor runs on an empty chain, the supply stays with the factory, and the runtime is within EIP-170 with no DELEGATECALL, CALLCODE or SELFDESTRUCT.\n\n`git status` shows launch.json as the only new tracked-path file. The other untracked entries are sandbox device nodes, not repository changes. No submodules exist, and nothing was deployed, broadcast or merged.","treeHash":"ce484c3a23368186f5cd98b0c253ff33b58272ef","usage":{"cachedInputTokens":354050,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":6063,"runtime":"claude","turns":16,"wallClockMs":116312}},{"artifacts":[],"attempt":1,"bundleHash":"3811d87ced1fd7ddee3f4b2686425273fcbcae5c5b98b598302dfb9a93f1abd8","device":"e3a598aae0640402","findings":[],"hash":"76c79dbd9ee234f8a00671100d8f54fe8f125785437d86da9d7e23fd1db666a9","nodeId":"a75fe580-174c-43bd-b303-6b951b8a7e1a","outcome":"completed","summary":"The work is complete and verified. Summary:\n\n**What was wrong.** The pass gave each mystery token one Chainlink VRF request and finalized strictly in mint order, assuming an unpaid request waits forever. Chainlink's own docs say an unpaid request expires after 24 hours and a failed callback is never retried, so one dropped request would have frozen every later reveal permanently. The stuck state reproduces in the first half of `test_ExpiredRequestBlocksQueueUntilRerequest`.\n\n**The fix** in `src/BMCIlluminatiPass.sol` is a permissionless `rerequestRandomness(tokenId)`. It only works for a token that holds no randomness at all, only once its request is at least 72 hours old (three times Chainlink's window), unmaps the abandoned request id before opening a new one, and records the new request time. Finalization order and callback handling are unchanged. The owner has no lever the public lacks. The design, considered alternatives, fairness argument and trust assumptions are in `docs/VRF-RECOVERY-DESIGN.md`, written before the code.\n\n**Other audit findings, all reproduced and fixed with tests:**\n- Genesis artwork CID is now a compile-time constant. The value is the one the requester committed in the fork test, and I verified via the Pinata gateway that genesis-01, 40 and 77 are byte-identical to the repo artwork. The owner setter is gone; the two read-only views the mint site calls remain.\n- Ownable2Step replaces single-step Ownable.\n- NatSpec and README no longer claim unpaid requests wait indefinitely. Daily funding duty and the 72-hour re-request are documented.\n\n**Launch requirements.** `src/LaunchToken.sol` (\"Illuminati Pass\", IPASS) is the fixed-supply launch token with tests. The pass keeps its three-argument constructor and makes no external call in it. I ran the pinned floor tests against the real creation code in a scratch harness: constructors run on an empty chain, supply stays with the factory, no forbidden opcodes, runtime 23,416 bytes.\n\n**Tests.** `forge build` is clean and `forge test` gives 64 passed, 0 failed, 1 skipped. The skip is the requester's mainnet fork test, which I changed to skip rather than revert when no fork is present. New `test/VRFRecovery.t.sol` covers expired requests, the timeout boundary, late and duplicate callbacks, out-of-order fulfilment with a re-request, stored-word and front-running manipulation attempts, owner privilege probes, and a 500-mint simulation with expired requests that still yields 77 Genesis, 423 Standard and 201,000,000 BMC burned.\n\n**Guarantee changes** are reported in `docs/PULL-REQUEST.md` and the re-review in `docs/SECURITY-REVIEW.md` Part C. The re-review was written adversarially by me against my own change, so it is not independent; IMD's final review is the independent one.\n\n**Not done, and why.** The branch and PR could not be pushed or opened: this machine has no GitHub credentials or `gh`. The remote already has an empty `security/vrf-recovery` branch, and the full PR title and body are in `docs/PULL-REQUEST.md` for the requester to open. Nothing was deployed, broadcast, approved or merged, and the website was not changed. `ADAPTATION.md` lists every change with its reason.","treeHash":"a1b1510726012537110b19c1292ee63c6dc945e8","usage":{"cachedInputTokens":1957318,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":82861,"runtime":"claude","turns":82,"wallClockMs":1172478}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8af9903f4ad1eed0","findings":[{"citation":"resolved","description":"finalizeReveals() only advances while the head-of-queue token (GENESIS_PHASE_SUPPLY + revealedCount + 1) is in RevealStatus.RandomnessStored. A mystery token gets exactly one VRF request, opened in mintMystery(); nothing in the contract can open another one, and rawFulfillRandomWords() only accepts the id stored in tokenIdOfRequest. Verified Chainlink behaviour (docs.chain.link/vrf/v2-5/overview/subscription): 'If your balance is below the minimum, your requests remain pending for up to 24 hours before they expire' and pending requests 'automatically process as long as they have not expired'. The VRFCoordinatorV2_5 source (npm @chainlink/contracts 1.4.0) has no on-chain expiry or retry: the node simply stops serving the request after 24h, s_consumers[consumer][subId].pendingReqCount stays incremented forever (so the subscription owner can never cancelSubscription() and recover the LINK/ETH balance), and fulfillRandomWords deletes the commitment before calling the consumer so a reverting callback is also final. Chainlink's security page adds that the VRF service never calls fulfillRandomWords a second time. The contract's own NatSpec (lines 36-39) and README ('Unavailable randomness') assume the opposite: that an unfunded request waits indefinitely and is fulfilled once anyone funds the subscription. That assumption is false after 24 hours. Consequence: a single request that the subscription could not pay for within 24h (a mint rush that drains the subscription, a gas-price spike that raises the estimated fulfilment cost above the balance, the subscription owner forgetting to top up over a weekend, Chainlink deprecating the configured key hash) leaves that token AwaitingRandomness forever, and because finalization is strictly in mint order every later token stays 'Pending Reveal' too, even when its own random word was delivered and stored. Up to 459 holders who burned 350,000 BMC each never receive their Genesis/Standard outcome; 37 Genesis outcomes can be stranded in the pool. There is no owner or user action that repairs this. The size budget also matters for the fix: the runtime is 23,529 bytes today; a minimal recovery function brought it to 24,257 bytes in a local trial (319 bytes under EIP-170), so the adapter must re-check `forge build --sizes`. Recovery design that preserves fairness (what the attached proof binds to): a permissionless rerequestRandomness(tokenId) that (1) only works for tokens in AwaitingRandomness, so a stored or revealed word can never be replaced; (2) only works once the current request is older than a timeout well beyond Chainlink's 24h window (72h recommended), so no legitimate fulfilment can still be in flight and nobody can watch the mempool for an unfavourable word and front-run it with a re-request; (3) deletes tokenIdOfRequest for the abandoned id so a late delivery is rejected, and binds the token to the new id; (4) records the new request time so the next re-request needs another full timeout (bounds subscription spend to one extra request per token per window); (5) leaves finalizeReveals strictly in mint order. Trust assumptions after the fix: VRF words stay unbiased; the only new power anyone gains is to pay for one fresh request per expired token per timeout window using the subscription's funds; the owner gains nothing (no selective retry, no reroll). Chainlink's docs state that 'any re-request ... is an incorrect use of VRF v2.5' because it lets a party discard unfavourable randomness; the timeout gate is what removes that ability here, since there is no randomness to discard for a request the node has dropped. Existing tests do not cover this: test_UnavailableRandomnessStaysPendingNoFallback asserts the stuck state as desired behaviour, and both MockVRFCoordinator and Chainlink's VRFCoordinatorV2_5Mock never expire a request (the mock's pendingRequestExists is a stub returning false).","line":321,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: phase 1 complete (40 Genesis minted), VRF configured. Calls: alice mintMystery() -> token 41, request R41; bob mintMystery() -> 42, R42; carol mintMystery() -> 43, R43. Coordinator delivers R42 and R43 (both become RandomnessStored); R41 is never delivered (subscription unfunded for >24h, Chainlink drops it). Warp 30 days. finalizeReveals(10) returns 0; tierOf(42) and tierOf(43) stay Tier.Pending although their words are stored; no function on the contract can open a new request for 41 (a call to rerequestRandomness(41) hits no function and reverts). Expected: tokens whose randomness has been delivered are eventually revealed, and a token whose request Chainlink dropped can obtain fresh randomness without anyone choosing the outcome. Actual: 41, 42, 43 and all 457 later mystery mints are 'Pending Reveal' forever. Proof: test/scratch/ExpiredVRFRequestBlocksQueue.t.sol fails on the current code with 'no recovery path for an expired VRF request' and passed against a local reference patch implementing the design above (permissionless, 72h timeout, old id unmapped, mint order kept).","severity":"high","snippet":"        while (done < maxCount && tokenId <= totalMinted && _revealStatus[tokenId] == RevealStatus.RandomnessStored) {","title":"One expired Chainlink VRF request permanently blocks every later mystery reveal (no recovery path)"},{"citation":"resolved","description":"The launch rules forbid application contracts that are configured after deployment rather than in a nonpayable constructor, except for values only the requester holds that the brief does not give (an outside service's subscription id, feed or relayer). setVRFConfig falls under that exception: the subscription id only exists once the requester creates it on the coordinator, so an owner-only setter is the accepted shape and should stay. setGenesisImagesCID does not: the IPFS directory CID is the requester's own, fixed artwork reference (test/BMCMainnetFork.t.sol:38 already hard-codes bafybeihfihwwpev4gkqjuqawgtptsv3ynzsokclo6odsolvoa65aqaac2u) and nothing about it depends on a later step. As written the contract leaves the whole collection in an unusable state until the owner acts, and lets the owner choose, after launch, which images the 77 Genesis tokens point to. Because the manifest only supports address/uint/bool/bytes32 constructor arguments and a CIDv1 string does not fit in bytes32, the adapter should either hard-code the final CID as a constant in the contract, or pass the CID's 32-byte multihash digest as a bytes32 constructor argument and rebuild the base32 'bafybei...' string on-chain. (Whether the hard-coded CID actually resolves could not be checked from this environment: two public gateways returned HTTP 429.)","line":186,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: fresh deployment via DeployBMCIlluminatiPass.deploy(cfg). Call: any account mintGenesis() with sufficient BMC and allowance. Actual: revert GenesisCIDNotSet(); minting stays impossible until the owner calls setGenesisImagesCID. Owner then calls setGenesisImagesCID(\"bafy...anything\") with any string; tokenURI(1) after the first mint points every Genesis image at that owner-chosen location, permanently. Expected under the launch rules: the artwork location is fixed by the constructor so the contract is complete and immutable at deployment.","severity":"medium","snippet":"    function setGenesisImagesCID(string calldata cid) external onlyOwner {","title":"Genesis artwork location is configured after deployment instead of in the constructor"},{"citation":"resolved","description":"The contract uses OpenZeppelin Ownable, not Ownable2Step. The owner's VRF-config power is the only way to react to Chainlink changes (key hash / gas lane deprecation, coordinator subscription migration, raising callbackGasLimit if fulfilments start failing). transferOwnership(wrongAddress) or renounceOwnership() therefore does not just lose a role: once the configured key hash stops being served, mintMystery still accepts 350,000 BMC burns and opens requests that are never fulfilled, which (with the high finding above) is exactly the state that bricks the reveal queue, and nobody can repoint the contract. Fix: Ownable2Step so the new owner must accept, and document that renouncing ownership should only happen after the 460th mystery mint is revealed.","line":174,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: 40 Genesis minted, vrfConfig set to key hash K. Calls: owner transferOwnership(0x000...dEaD) (typo or compromised key). Later Chainlink retires lane K. alice mintMystery(): BMC is burned, a request on lane K is opened and never fulfilled. owner (now 0xdEaD) cannot call setVRFConfig, so every later mystery mint also burns BMC for a request that is never served. Expected: a two-step transfer makes the irreversible hand-off impossible by mistake.","severity":"low","snippet":"        Ownable(initialOwner)","title":"Single-step Ownable: a mistaken ownership transfer freezes the VRF configuration and can end mystery minting"},{"citation":"resolved","description":"Lines 36-39 and README 'Unavailable randomness' state that a pending request is fulfilled once the subscription is funded again. Chainlink's VRF v2.5 subscription documentation says requests with insufficient balance 'remain pending for up to 24 hours before they expire' and only process afterwards 'as long as they have not expired'. The statement is the stated trust model buyers rely on and it is wrong; it also hides the operational requirement that the subscription must never be under-funded for a full day while mystery mints are open. Correct the documentation alongside the recovery mechanism and add the 24h window to 'Operational responsibilities'.","line":38,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: mystery minting open, subscription balance 0. alice mintMystery() at T0 (request accepted by the coordinator). Nobody funds the subscription until T0 + 25h. Documented expectation: funding at T0+25h leads to fulfilment. Actual per Chainlink docs: the request expired at T0+24h and is never fulfilled; the token stays AwaitingRandomness forever.","severity":"low","snippet":"///  Chainlink VRF v2.5 requests remain pending while the subscription is unfunded and are fulfilled","title":"NatSpec and README assert that unfunded VRF requests wait indefinitely; Chainlink expires them after 24 hours"}],"hash":"92cf57b6213a488a639267dc5f9bfe54232251dec6e0cd43b23cb6dbec1d629e","nodeId":"f2388831-1882-48c7-ab7e-d95614059c6e","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json` in the repository root, the proof test is under `test/scratch/`, and no tracked file was changed.\n\n**What I read.** All three source files (`BMCIlluminatiPass.sol`, `StandardArt.sol`, `IBMCToken.sol`), the deploy script, all tests and mocks, the README and security review, the vendored Chainlink v2.5 mock, and the pinned Pashov, x-ray and Trail of Bits references. I also fetched the real `VRFCoordinatorV2_5` source and Chainlink's subscription and security documentation to verify the expiry behaviour independently. Not reachable: the live BMC token and mainnet coordinator (no fork RPC), and the Genesis CID hard-coded in the fork test, since two IPFS gateways returned HTTP 429.\n\n**Findings, most severe first:**\n\n- **High. One expired VRF request freezes every later reveal.** Chainlink documents that under-funded requests \"remain pending for up to 24 hours before they expire\", the coordinator has no on-chain retry, and the contract never opens a second request for a token. Because `finalizeReveals` only advances past the head of the mint-order queue, a single dropped request for token 41 leaves tokens 42 to 500 \"Pending Reveal\" forever even when their words were stored. The contract's NatSpec and README assume the opposite. The proof fails on the current code at the stated assertion and passed against a local reference patch, which I reverted. It binds the fix to a permissionless, timeout-gated `rerequestRandomness` that keeps mint order, rejects stored or revealed tokens and unmaps the abandoned request id. That trial also pushed runtime size to 319 bytes under the EIP-170 limit, which the adapter must watch.\n- **Medium. Genesis image CID is set after deployment.** Launch rules forbid post-deployment configuration except for values only the requester holds. The VRF subscription setter qualifies for that exception. The CID does not, since the fork test already hard-codes it, and until the owner sets it no mint is possible.\n- **Low. Single-step Ownable.** A mistaken transfer freezes the VRF config, after which mystery mints keep burning BMC for requests that may never be served.\n- **Low. Documentation misstates the pending-request guarantee.** The 24-hour expiry is absent from the trust model and operational responsibilities.\n\n**Randomness and fairness assessment.** The current draw is sound: order-independent, no reroll path, immutable coordinator, idempotent duplicate callbacks. The recommended recovery keeps those guarantees because the re-request only opens long after any legitimate fulfilment could arrive, so there is no unfavourable word to discard, and the owner gains no selective power. The adapter also needs to add the missing `LaunchToken`, which the launch requires and which this repository does not contain.","treeHash":null,"usage":{"cachedInputTokens":1643269,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":35639,"runtime":"claude","turns":38,"wallClockMs":650908}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"bd7adba3a8045853","findings":[{"citation":"resolved","description":"Boundary x precision (documentation only, no safety impact). The NatSpec says one reveal was measured at ~85k gas worst case; the 110k budget is described as conservative. Measured on this tree (forge, solc 0.8.26, optimizer 200, cancun) an external call to finalizeReveals(1) costs 111,972 gas for the first reveal and 116,530 for the next (about 109k-113k inside the call), so GAS_PER_REVEAL is roughly the real cost, not a conservative margin, and the inline batch is only correct because the 63/64 rule leaves ~1.5% headroom: in a 1k-step sweep of callbackGasLimit from 150k to 700k (worst case: very first reveal lands Genesis so revealedCount and mysteryGenesisRevealed are fresh slots, four more words queued behind it) every attempted inline batch succeeded and no callback ever failed, with batch thresholds at exactly 173k/283k/393k/503k callback gas. The downstream consequence is the README runbook (README.md line 147: 'setVRFConfig(subscriptionId, keyHash, 500000, 3, nativePayment)' ... '500k callback gas lets each callback finalize up to 4 reveals'): with 500,000 callback gas the store path leaves ~467k, (467k - 30k) / 110k = 3, so at most 3 reveals finalize inline; 4 needs >= 503k. The word is never lost (stored before the try, permissionless finalizeReveals covers the rest), so fairness and supply invariants are unaffected. Fix: correct the comment to the measured ~110k-117k, and either tell the operator to set >= 510k (or 2.5M) callback gas for 4 inline reveals, or state that 500k gives 3.","line":99,"path":"src/BMCIlluminatiPass.sol","reproduction":"Deploy BMCIlluminatiPass with MockBMC and MockVRFCoordinator (test/mocks), setVRFConfig(42, keyHash, 500_000, 3, false), mint 40 Genesis then mystery tokens 41..45. Store words for 42..45 via vrf.fulfillWithGas(req, word, 95_000) (store only, no inline reveal). Then vrf.fulfillWithGas(req41, word, 500_000): expected per README 'up to 4 reveals' => revealedCount == 4; actual revealedCount == 3. Repeat with 503_000 => 4. Measure gas: after two reveals, g=gasleft(); pass.finalizeReveals(1); g-gasleft() == 111_972 (next call 116_530), versus the comment's '~85k'. Sweep callbackGasLimit 150_000..700_000 step 1_000 with the first-ever reveal landing Genesis: every callback returns success and revealedCount steps 0->1 at 173k, 2 at 283k, 3 at 393k, 4 at 503k; no failed callback and no wasted inline attempt.","severity":"info","snippet":"    /// @dev Conservative gas budget per reveal (measured ~85k worst case) and reserve for the return path.\n    uint256 public constant GAS_PER_REVEAL = 110_000;","title":"Inline-reveal gas budget comment and README runbook overstate what a callback finalizes (reveal costs ~110k, not ~85k; 500k callback gas yields 3 inline reveals, not 4)"}],"hash":"c79071ff9cc6639d50680b2d84938d1dee4c553c8326e0583f4b845746c658b1","nodeId":"d30721cb-6c5a-40e9-ad20-0777f3e38ad0","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` in the repository root, the scratch tests are removed, and no project file was changed.\n\n**Outcome:** the math, boundary and numerical-gap area of the BMC Illuminati Pass contract holds. I found one documentation-level defect and no arithmetic, rounding, overflow, off-by-one or external-boundary defect that affects fairness, supply or funds.\n\n**What I verified**\n\n- **Draw arithmetic.** The virtual Fisher-Yates over 460 slots is correct, including the sentinel encoding and the `r == remaining-1` case. The divisor is never zero, artwork ids land in 41..77 and 1..423, and the 500-mint simulations (in order, shuffled, and with expired requests) produce exactly 77 Genesis and 423 Standard with every artwork once. Modulo bias is below 2^-247 as documented.\n- **Boundaries.** Phase gates at 40 and 500, the exact-burn delta check, the `MAX_TOTAL_BURN` assert (reached exactly at sell-out, never exceeded), and the 72-hour re-request gate (rejects at +72h-1, allows at +72h) all behave as the design document states.\n- **Real external dependencies.** I ran the project's mainnet fork test against a public RPC: the real BMC token's `burnFrom` reduces supply by exactly the price. I also read the live VRF v2.5 coordinator: minimum confirmations 3, max callback gas 2,500,000, max confirmations 200, matching the contract's `setVRFConfig` bounds.\n- **Callback gas arithmetic.** A 1k-step sweep of callback gas from 150k to 700k, with the worst case of a first-ever Genesis reveal and four words queued, never produced a failed callback and every attempted inline batch succeeded. A stored word is never lost.\n\n**The one finding (info):** the NatSpec on `GAS_PER_REVEAL` says a reveal was measured at ~85k gas, but it costs about 110k to 117k, so the budget is tight rather than conservative. As a consequence the README runbook's claim that 500k callback gas finalizes up to 4 reveals is wrong: 500k gives 3, and 4 needs at least 503k. There is no safety impact because permissionless finalization covers the remainder.\n\n**Static-analysis leads:** the Slither reentrancy lines concern calls into the fixed BMC token and the coordinator before state writes, both guarded by `nonReentrant` and verified against the real token. The Aderyn \"storage array edited with memory\" line is a false positive, since `vrfConfig` is copied to memory. None reproduce as defects.\n\n**Coverage:** all 17 listed entry points have a row (16 hold, one carries the info finding), plus four invariant rows. Nothing in my area was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1153820,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":40857,"runtime":"claude","turns":35,"wallClockMs":643456}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9812c5faf226f7c7","findings":[{"citation":"resolved","description":"tokenURI() (line 472: `if (tier == Tier.Pending) { json = _pendingJson(tokenId); }`) serves byte-identical 'Pending Reveal' metadata for RevealStatus.AwaitingRandomness and RevealStatus.RandomnessStored. In the RandomnessStored state the random word is public (storage + RandomnessStored event) and the outcome is a pure function of public state: for the head of the queue it is exactly keccak256(abi.encode(word, tokenId)) % remaining applied to the current _slotSwap table; for later tokens it is determined with probability 1 - 1/remaining until the stuck predecessor is drawn. The description string therefore asserts something false ('not yet determined on-chain') and marketplaces that render tokenURI show the token as an undetermined mystery. Economics x asymmetry seam (Trust Gap guide, seam 2): the party who computes the outcome (seller, or a buyer who reads the event) trades a known Standard token against a counterparty who sees 'Pending Reveal'. This window existed in the arrived code only for the few seconds between a low-gas callback and permissionless finalization; the recovery design makes it structural: while one token's request is dropped, every later token's word accumulates as RandomnessStored for at least VRF_REREQUEST_TIMEOUT (72h) plus the time until a re-request and its fulfilment, and none can be finalized because finalizeReveals is strictly in mint order (line 347). The owner has no lever here; this is an unprivileged, information-only edge. Fairness of the draw itself is unaffected (the word is fixed and cannot be replaced), so severity is low. Suggested minimal fix that preserves the design: have tokenURI (or _pendingJson) distinguish RandomnessStored from AwaitingRandomness, e.g. an attribute 'Reveal Status' = 'Randomness delivered, awaiting in-order finalization' and drop the 'not yet determined on-chain' sentence for that state; optionally expose the computed tier for the queue head. Alternatively document in README that 'Pending Reveal' tokens may have a publicly computable outcome and that buyers should check revealStatusOf/RandomnessStored events and call finalizeReveals before trading.","line":545,"path":"src/BMCIlluminatiPass.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {BMCIlluminatiPass} from \"src/BMCIlluminatiPass.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC20Burnable} from \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol\";\nimport {VRFV2PlusClient} from \"@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol\";\n\ncontract ScratchBMC is ERC20, ERC20Burnable {\n    constructor() ERC20(\"BMC\", \"BMC\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\ninterface IRawFulfill {\n    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;\n}\n\ncontract ScratchCoordinator {\n    uint256 public next = 1000;\n    mapping(uint256 => address) public consumerOf;\n\n    function requestRandomWords(VRFV2PlusClient.RandomWordsRequest calldata) external returns (uint256 id) {\n        id = next++;\n        consumerOf[id] = msg.sender;\n    }\n\n    function fulfillWithGas(uint256 requestId, uint256 word, uint256 gas) external returns (bool ok) {\n        uint256[] memory words = new uint256[](1);\n        words[0] = word;\n        (ok,) = consumerOf[requestId].call{gas: gas}(abi.encodeCall(IRawFulfill.rawFulfillRandomWords, (requestId, words)));\n    }\n}\n\n/// @dev A mystery token whose VRF word is already stored on-chain (RandomnessStored) is served the\n///      same \"outcome not yet determined on-chain\" metadata as a token still awaiting randomness,\n///      although its outcome is now fully determined by public state. The test asserts that the\n///      metadata reflects the stored state; it fails on the current code because both URIs are\n///      byte-identical.\ncontract StoredWordMetadataTest is Test {\n    ScratchBMC bmc;\n    ScratchCoordinator vrf;\n    BMCIlluminatiPass pass;\n    address owner = makeAddr(\"owner\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        bmc = new ScratchBMC();\n        vrf = new ScratchCoordinator();\n        pass = new BMCIlluminatiPass(owner, address(bmc), address(vrf));\n        vm.prank(owner);\n        pass.setVRFConfig(1, keccak256(\"lane\"), 300_000, 3, false);\n        for (uint256 i = 0; i < 40; i++) {\n            address who = address(uint160(0x1000 + i));\n            bmc.mint(who, 1_000_000e18);\n            vm.startPrank(who);\n            bmc.approve(address(pass), 1_000_000e18);\n            pass.mintGenesis();\n            vm.stopPrank();\n        }\n    }\n\n    function test_StoredWordIsReflectedInMetadata() public {\n        bmc.mint(alice, 350_000e18);\n        vm.startPrank(alice);\n        bmc.approve(address(pass), 350_000e18);\n        (uint256 id, uint256 req) = pass.mintMystery();\n        vm.stopPrank();\n        assertEq(id, 41);\n\n        string memory awaitingURI = pass.tokenURI(id);\n\n        // Word delivered with too little gas to finalize inline: stored, not finalized.\n        uint256 word = 31337;\n        assertTrue(vrf.fulfillWithGas(req, word, 95_000));\n        assertEq(uint8(pass.revealStatusOf(id)), uint8(BMCIlluminatiPass.RevealStatus.RandomnessStored));\n\n        // The outcome is now fully determined by public state: anyone can compute it before finalization.\n        uint256 r = uint256(keccak256(abi.encode(word, id))) % 460;\n        BMCIlluminatiPass.Tier predicted = r < 37 ? BMCIlluminatiPass.Tier.Genesis : BMCIlluminatiPass.Tier.Standard;\n\n        string memory storedURI = pass.tokenURI(id);\n\n        // Finalize and confirm the prediction was exact.\n        assertEq(pass.finalizeReveals(1), 1);\n        assertEq(uint8(pass.tierOf(id)), uint8(predicted), \"outcome was computable before finalization\");\n\n        // Expected: metadata for a token whose word is on-chain differs from \"awaiting randomness\"\n        // metadata (it must not claim the outcome is undetermined). Actual: byte-identical.\n        assertTrue(\n            keccak256(bytes(storedURI)) != keccak256(bytes(awaitingURI)),\n            \"RandomnessStored token served the same 'not yet determined' metadata as an AwaitingRandomness token\"\n        );\n    }\n}","reproduction":"State: 40 Genesis mints done, VRF configured. 1) alice calls mintMystery() -> token 41, request R. 2) Coordinator delivers R with 95_000 gas (enough to store the word, not to finalize): rawFulfillRandomWords(R,[31337]) -> revealStatusOf(41) == RandomnessStored, event RandomnessStored(41,R,31337). 3) Anyone computes r = keccak256(abi.encode(31337, 41)) % 460 and knows tier = r < 37 ? Genesis : Standard (for word 31337 the test confirms the prediction matches tierOf(41) after finalizeReveals(1)). 4) tokenURI(41) at this point is byte-identical to tokenURI(41) before step 2 and contains 'The outcome (Genesis or Standard) is not yet determined on-chain.' Expected: metadata for a token holding a stored word reflects that state (or at least does not assert the outcome is undetermined). Actual: identical 'Pending Reveal' metadata; the outcome is determined and computable by anyone. With a stuck predecessor (e.g. token 41's request dropped), tokens 42.. stay in this state for >= 72h.","severity":"low","snippet":"            ' - Pending Reveal\",\"description\":\"A BMC Illuminati Pass mystery mint awaiting Chainlink VRF randomness. The outcome (Genesis or Standard) is not yet determined on-chain. Metadata becomes permanent once revealed.\",\"image\":\"data:image/svg+xml;base64,',","title":"Pending metadata claims the outcome is 'not yet determined on-chain' for tokens whose VRF word is already stored, creating a secondary-market information asymmetry that the 72h recovery window lengthe"}],"hash":"cf026dd6b00537befec88a3aa0490f59fa927398e0f002c4a6502c2bdeb4ed65","nodeId":"7cddb5a1-3ac8-44b7-9184-16e66dc0199c","outcome":"completed","summary":"The review is complete. The findings file is written and no tracked files were changed.\n\n**Outcome.** The access-control, trust-gap and asymmetry audit of the VRF recovery fix found no permission bypass, no owner reroll or selective-retry lever, and no asymmetry that breaks the draw. One low-severity information asymmetry is reported, with a scratch test that fails on the current code.\n\n**Finding 1 (low), `src/BMCIlluminatiPass.sol:545`.** Pending metadata says the outcome is \"not yet determined on-chain\" even when the token is in the RandomnessStored state. At that point the word is public and the outcome is computable by anyone, so a seller can trade a known Standard token against a buyer who sees \"Pending Reveal\". The 72-hour recovery window makes this structural: while one request is stuck, every later token sits in that state for days because finalization is strictly in mint order. The draw itself stays fair. Reproduction: deliver a word with 95k callback gas, compute the tier from the stored word, confirm it matches after finalization, and observe the token URI is byte-identical to the awaiting-randomness URI. The proof test is at `test/scratch/StoredWordMetadata.t.sol`.\n\n**What held, by area.**\n- Access control: every state-changing entry point was mapped. The only owner powers are `setVRFConfig` and the two-step ownership transfer. The config setter cannot touch open requests, stored words, request maps or the timeout. The re-request gate is identical for the owner and the public. Renouncing ownership freezes the config, which is documented and has no unprivileged amplifier.\n- Trust gap: the owner can delay reveals through subscription funding or a dead lane but cannot bias them. The callback has no holder-controlled code, so a callback failure cannot be value-dependent. Re-request spend is bounded to genuinely stuck tokens once per 72 hours.\n- Asymmetry: mint and re-request write the same request state through one helper. A coordinator revert during re-request restores the old mapping. Fulfilment for the abandoned id reverts, duplicates are ignored, and the Fisher-Yates swap is correct at the last-slot edge.\n\n**Coverage.** All 17 listed entry points have rows, plus three invariant rows. Baseline suite: 64 passed, 1 skipped. The claim that Chainlink never serves a request older than 72 hours remains a documented trust assumption, not a code defect, since a late word is rejected at the cost of one fulfilment fee.","treeHash":null,"usage":{"cachedInputTokens":918694,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":33884,"runtime":"claude","turns":30,"wallClockMs":459721}},{"artifacts":[],"attempt":1,"bundleHash":"b12071747866de05a6b2bc8ede79257076995e9ff23b7c7a5b334a451c5c49fa","device":"e8e860c7f2306473","findings":[{"description":"The comment on GAS_PER_REVEAL says a reveal was measured at ~85k worst case. Measured with the project's own mock on this code, the first mystery reveal that lands on a Genesis outcome costs ~156k gas (first non-zero write of revealedCount, mysteryGenesisRevealed, _slotSwap[r], _tier and _artworkId, each 22.1k cold, plus the status change, the deleted word and the event; refunds only apply at the end of the transaction, so they do not help inside the callback). The smallest callback gas limit at which that reveal completes inline is ~173k; between ~165k and ~172k, rawFulfillRandomWords computes batch = 1, calls this.finalizeReveals(1), and the call runs out of gas inside the try/catch. The word is stored before the attempt and the catch swallows the failure, so no randomness is lost and the callback still succeeds; the token is simply left in RandomnessStored and must be finalized by a permissionless finalizeReveals call. The impact is a wasted inline attempt whose gas the VRF subscription pays for, and documentation that overstates how often reveals complete inside the callback. No fairness or supply property is affected: the tests in test/VRFRecoveryEdges.t.sol (test_InlineFinalizeOutOfGasKeepsStoredWordAndSameOutcome, testFuzz_CallbackNeverRevertsAtAnyAllowedGas) show the stored word survives for every configurable gas limit and yields the same outcome as an inline reveal. Suggested fix: set GAS_PER_REVEAL to a measured worst case (e.g. 160k) and correct the comment, which also makes CALLBACK_REVEAL_BATCH reachable only at limits that can actually pay for it.","line":100,"path":"src/BMCIlluminatiPass.sol","reproduction":"Deploy BMCIlluminatiPass with MockBMC and MockVRFCoordinator, setVRFConfig(42, keyHash, 300_000, 3, false), complete the 40 Genesis mints, mintMystery once (token 41, request r). Choose word w such that uint256(keccak256(abi.encode(w, 41))) % 460 < 37 (w = smallest such value). Call vrf.fulfillWithGas(r, w, 165_000). Expected per the NatSpec budget (110k per reveal, batch = 1 computed from ~133k available): token 41 revealed inline. Actual: callback returns success, revealStatusOf(41) == RandomnessStored, revealedCount == 0; finalizeReveals(1) afterwards reveals it as Genesis with the same artwork a 400k callback produces inline. Sweeping the callback gas from 150k to 400k in 1k steps: the callback never reverts and the word is always stored; inline reveal first succeeds at 173k.","severity":"info","title":"GAS_PER_REVEAL (110k) underestimates a worst-case reveal (~156k), so the inline finalize attempt in the VRF callback can run out of gas and is wasted"}],"hash":"deda0a2372754ca88e3962317005225a5f5e71d846b1b40c0ef0823153b07f44","nodeId":"0f31b9a6-a606-4492-afd1-759dfb2ba971","outcome":"completed","summary":"Build is clean and the whole suite passes. Only two new files sit in the working tree, both under `test/`, so nothing is out of scope.\n\n**What I did**\n\nI tested the accepted VRF recovery implementation as an adversary rather than re-testing its happy paths. The Chainlink docs confirm the premise of the 72h gate: underfunded requests stay pending for up to 24 hours and then expire. Two new test files were added.\n\n- **`test/BMCIlluminatiPass.invariant.t.sol`** drives the pass with a handler over random call sequences from six actors plus the owner: both mint phases, full-gas and starved deliveries in any order, duplicate callbacks, late callbacks for abandoned ids, unknown ids, deliveries from non-coordinators, finalization, time travel, re-requests by holders, strangers and the owner, config changes and transfers. Eight invariants cover supply caps, burn conservation against ghost accounting, reveal counts and pool sizes, Genesis tokens being fixed forever, strict mint-order reveal, immutability of revealed outcomes and artwork uniqueness, request-mapping consistency, and that a delivered word is never discarded or re-requested. It runs with fail-on-revert so every unexpected revert or handler assertion is a failure. Result: 48 runs, 0 reverts, all invariants hold.\n- **`test/VRFRecoveryEdges.t.sol`** pins edges the existing suites did not: the callback at the minimum configurable gas, a fuzz over the whole allowed gas range showing the callback never reverts and the word is always stored, the inline-finalize budget running dry, a callback that failed at the coordinator being recoverable only after the timeout, reentrancy from the mint receiver into re-request and finalize, request id zero, extra random words, zero and huge finalize counts, batching independence, queue position after a re-request, and a fuzz showing outcomes are independent of the whole recovery history.\n\n**Test results**\n\n| Suite | Result |\n| --- | --- |\n| Whole repository | 88 passed, 0 failed, 1 skipped |\n| Skipped | the mainnet fork test, which skips cleanly without an RPC |\n\n**Finding reported, not asserted**\n\nOne informational finding is in `.imd-findings.json`. The per-reveal gas budget in the callback is 110k with a comment claiming 85k measured, but a first reveal that lands on Genesis costs about 156k, so between roughly 165k and 172k of callback gas the inline attempt runs out of gas inside the try/catch and is wasted. The word survives, the callback succeeds, and the token finalizes permissionlessly with the identical outcome, so fairness and supply are unaffected. I did not find a defect in the randomness or fairness guarantees: no path replaces a stored word, no caller including the owner can re-request inside the timeout or for a token holding a word, and late deliveries for abandoned ids are always rejected.\n\n**Still owed outside this environment**: the fork test against live mainnet state, which needs an RPC the verifier does not have.","treeHash":"b15c7e62ca43c89078143732651ba19899862873","usage":{"cachedInputTokens":1581395,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":58641,"runtime":"claude","turns":36,"wallClockMs":866285}}],"verification":[{"checks":[{"durationMs":6423,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.24s\nCompiler run successful!\n","passed":true},{"durationMs":735,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/BMCMainnetFork.t.sol:BMCMainnetForkTest\n[SKIP] test_RealBMCGenesisMint() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 627.85µs (97.41µs CPU time)\n\nRan 3 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsWholeSupplyToDeployer() (gas: 72397)\n[PASS] test_NoMintOrAdminSurface() (gas: 161035)\n[PASS] test_TransferMovesExactAmount() (gas: 93419)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.53ms (809.56µs CPU time)\n\nRan 5 tests for test/ChainlinkMockIntegration.t.sol:ChainlinkMockIntegrationTest\n[PASS] test_ExpiredRequestRecoveredThroughChainlinkMock() (gas: 1304112)\n[PASS] test_NotAConsumerReverts() (gas: 367218)\n[PASS] test_RequestAndFulfilThroughChainlinkMock() (gas: 779232)\n[PASS] test_SeveralRequestsOutOfOrderThroughChainlinkMock() (gas: 1950705)\n[PASS] test_UnfundedSubscriptionKeepsTokenPendingUntilFunded() (gas: 969068)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 13.23ms (4.62ms CPU time)\n\nRan 16 tests for test/VRFRecovery.t.sol:VRFRecoveryTest\n[PASS] testFuzz_RerequestedRevealAlwaysInInventory(uint256,uint32) (runs: 64, μ: 772693, ~: 770838)\n[PASS] test_DuplicateCallbackForNewRequestIsIgnored() (gas: 1030796)\n[PASS] test_ExpiredRequestBlocksQueueUntilRerequest() (gas: 2184794)\n[PASS] test_FrontRunningFulfilmentBeforeTimeoutFails() (gas: 861243)\n[PASS] test_FullMintOutWithExpiredRequestsPreservesInvariants() (gas: 273282819)\n[PASS] test_LateDeliveryForAbandonedRequestIsRejected() (gas: 963520)\n[PASS] test_OutOfOrderFulfilmentWithRerequestKeepsMintOrder() (gas: 3302442)\n[PASS] test_OwnerHasNoSelectiveRetryOrReroll() (gas: 1409444)\n[PASS] test_RerequestBeforeTimeoutRevertsExactlyAtBoundary() (gas: 636702)\n[PASS] test_RerequestBurnsNothingAndKeepsTokenIntact() (gas: 683488)\n[PASS] test_RerequestOutcomeIndependentOfCallerAndTiming() (gas: 1827152)\n[PASS] test_RerequestRevertsCleanlyWhenCoordinatorRejects() (gas: 755734)\n[PASS] test_RerequestRevertsUnlessTokenAwaitsRandomness() (gas: 1814978)\n[PASS] test_RerequestUsesCurrentConfigAfterLaneChange() (gas: 657426)\n[PASS] test_SecondRerequestNeedsAnotherFullTimeout() (gas: 1031951)\n[PASS] test_StoredUnfavourableWordCannotBeRerequested() (gas: 924795)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 459.12ms (498.26ms CPU time)\n\nRan 40 tests for test/BMCIlluminatiPass.t.sol:BMCIlluminatiPassTest\n[PASS] testFuzz_SingleRevealAlwaysInInventory(uint256) (runs: 64, μ: 10983205, ~: 10980487)\n[PASS] test_CallbackBatchIsGasAware() (gas: 12117171)\n[PASS] test_Constants() (gas: 82468)\n[PASS] test_DeployScriptConfig() (gas: 10593505)\n[PASS] test_DeployScriptRequiresOwner() (gas: 5673573)\n[PASS] test_ExcessAllowanceOnlyBurnsPrice() (gas: 341753)\n[PASS] test_FinalizationOrderCannotBeChosen() (gas: 11976948)\n[PASS] test_FirstMinterGetsLegendaryGenesis1() (gas: 2070355)\n[PASS] test_FulfillRevealsAndLocksMetadata() (gas: 17272738)\n[PASS] test_Full500MintSimulation() (gas: 278167273)\n[PASS] test_FullMintOutWithInOrderFulfillment() (gas: 244517071)\n[PASS] test_GenesisCIDIsConstantWithNoSetter() (gas: 1414317)\n[PASS] test_GenesisRevertsAfterPhase1() (gas: 10532826)\n[PASS] test_Mints2To40AreSequentialGenesis() (gas: 14428006)\n[PASS] test_MysteryBurnRevertsIfVRFRequestFails() (gas: 10666821)\n[PASS] test_MysteryRevertsDuringPhase1() (gas: 455835)\n[PASS] test_MysteryRevertsWhenVRFUnconfigured() (gas: 10520646)\n[PASS] test_NoOwnerMintOrWithdrawSurface() (gas: 81131)\n[PASS] test_OnlyCoordinatorCanFulfill() (gas: 10898309)\n[PASS] test_OutOfOrderFulfillmentFinalizesInMintOrder() (gas: 12199515)\n[PASS] test_OwnershipTransferIsTwoStep() (gas: 296032)\n[PASS] test_PendingMetadataIsNeutral() (gas: 13582598)\n[PASS] test_PermissionlessFinalizationWhenCallbackGasTooLow() (gas: 11065821)\n[PASS] test_Phase1CostsExactlyOneMillion() (gas: 390302)\n[PASS] test_Phase2CostsExactly350k() (gas: 10953731)\n[PASS] test_ReentrancyGenesisBlocked() (gas: 884690)\n[PASS] test_ReentrancyMysteryBlocked() (gas: 11367640)\n[PASS] test_RevertInsufficientAllowance() (gas: 179285)\n[PASS] test_RevertInsufficientBalance() (gas: 193157)\n[PASS] test_RevertNoAllowance() (gas: 144231)\n[PASS] test_RevertWhenTokenDecimalsUnexpected() (gas: 728429)\n[PASS] test_RevertWhenTokenDoesNotActuallyBurn() (gas: 744895)\n[PASS] test_StandardArtCombinationsUnique() (gas: 1482872)\n[PASS] test_StandardArtCoversEveryTrait() (gas: 743721)\n[PASS] test_StandardArtRendersWithoutGenesisTraits() (gas: 30070221)\n[PASS] test_StandardMetadataAfterReveal() (gas: 19961345)\n[PASS] test_TransfersAreStandard() (gas: 469656)\n[PASS] test_UnavailableRandomnessStaysPendingNoFallback() (gas: 11347945)\n[PASS] test_VRFConfigValidation() (gas: 253065)\n[PASS] test_ViewsRevertForNonexistent() (gas: 33595)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 547.26ms (1.88s CPU time)\n\nRan 5 test suites in 549.88ms (1.02s CPU time): 64 tests passed, 0 failed, 1 skipped (65 total tests)\n","passed":true},{"durationMs":1553,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BMCIlluminatiPass.acceptOwnership()\",\"BMCIlluminatiPass.approve(address,uint256)\",\"BMCIlluminatiPass.finalizeReveals(uint256)\",\"BMCIlluminatiPass.mintGenesis()\",\"BMCIlluminatiPass.mintMystery()\",\"BMCIlluminatiPass.rawFulfillRandomWords(uint256,uint256[])\",\"BMCIlluminatiPass.renounceOwnership()\",\"BMCIlluminatiPass.rerequestRandomness(uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256,bytes)\",\"BMCIlluminatiPass.setApprovalForAll(address,bool)\",\"BMCIlluminatiPass.setVRFConfig(uint256,bytes32,uint32,uint16,bool)\",\"BMCIlluminatiPass.transferFrom(address,address,uint256)\",\"BMCIlluminatiPass.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".github/workflows/main.yml\":43,\".gitignore\":5,\"ADAPTATION.md\":66,\"README.md\":187,\"artwork/genesis/genesis-01.png\":7910,\"artwork/genesis/genesis-02.png\":6998,\"artwork/genesis/genesis-03.png\":7016,\"artwork/genesis/genesis-04.png\":6664,\"artwork/genesis/genesis-05.png\":7186,\"artwork/genesis/genesis-06.png\":6703,\"artwork/genesis/genesis-07.png\":6488,\"artwork/genesis/genesis-08.png\":6807,\"artwork/genesis/genesis-09.png\":6793,\"artwork/genesis/genesis-10.png\":6550,\"artwork/genesis/genesis-11.png\":6125,\"artwork/genesis/genesis-12.png\":6418,\"artwork/genesis/genesis-13.png\":6298,\"artwork/genesis/genesis-14.png\":5909,\"artwork/genesis/genesis-15.png\":6142,\"artwork/genesis/genesis-16.png\":5727,\"artwork/genesis/genesis-17.png\":6019,\"artwork/genesis/genesis-18.png\":399,\"artwork/genesis/genesis-19.png\":369,\"artwork/genesis/genesis-20.png\":359,\"artwork/genesis/genesis-21.png\":330,\"artwork/genesis/genesis-22.png\":385,\"artwork/genesis/genesis-23.png\":391,\"artwork/genesis/genesis-24.png\":411,\"artwork/genesis/genesis-25.png\":393,\"artwork/genesis/genesis-26.png\":390,\"artwork/genesis/genesis-27.png\":386,\"artwork/genesis/genesis-28.png\":402,\"artwork/genesis/genesis-29.png\":375,\"artwork/genesis/genesis-30.png\":369,\"artwork/genesis/genesis-31.png\":421,\"artwork/genesis/genesis-32.png\":338,\"artwork/genesis/genesis-33.png\":389,\"artwork/genesis/genesis-34.png\":365,\"artwork/genesis/genesis-35.png\":352,\"artwork/genesis/genesis-36.png\":384,\"artwork/genesis/genesis-37.png\":365,\"artwork/genesis/genesis-38.png\":438,\"artwork/genesis/genesis-39.png\":374,\"artwork/genesis/genesis-40.png\":411,\"artwork/genesis/genesis-41.png\":380,\"artwork/genesis/genesis-42.png\":377,\"artwork/genesis/genesis-43.png\":399,\"artwork/genesis/genesis-44.png\":385,\"artwork/genesis/genesis-45.png\":352,\"artwork/genesis/genesis-46.png\":343,\"artwork/genesis/genesis-47.png\":362,\"artwork/genesis/genesis-48.png\":408,\"artwork/genesis/genesis-49.png\":325,\"artwork/genesis/genesis-50.png\":433,\"artwork/genesis/genesis-51.png\":377,\"artwork/genesis/genesis-52.png\":376,\"artwork/genesis/genesis-53.png\":401,\"artwork/genesis/genesis-54.png\":389,\"artwork/genesis/genesis-55.png\":361,\"artwork/genesis/genesis-56.png\":332,\"artwork/genesis/genesis-57.png\":414,\"artwork/genesis/genesis-58.png\":405,\"artwork/genesis/genesis-59.png\":409,\"artwork/genesis/genesis-60.png\":405,\"artwork/genesis/genesis-61.png\":369,\"artwork/genesis/genesis-62.png\":366,\"artwork/genesis/genesis-63.png\":379,\"artwork/genesis/genesis-64.png\":425,\"artwork/genesis/genesis-65.png\":406,\"artwork/genesis/genesis-66.png\":413,\"artwork/genesis/genesis-67.png\":350,\"artwork/genesis/genesis-68.png\":404,\"artwork/genesis/genesis-69.png\":392,\"artwork/genesis/genesis-70.png\":378,\"artwork/genesis/genesis-71.png\":401,\"artwork/genesis/genesis-72.png\":355,\"artwork/genesis/genesis-73.png\":405,\"artwork/genesis/genesis-74.png\":394,\"artwork/genesis/genesis-75.png\":390,\"artwork/genesis/genesis-76.png\":424,\"artwork/genesis/genesis-77.png\":381,\"artwork/tools/generate_genesis.py\":206,\"docs/PULL-REQUEST.md\":64,\"docs/SECURITY-REVIEW.md\":90,\"docs/TEST-RESULTS.md\":94,\"docs/VRF-RECOVERY-DESIGN.md\":131,\"foundry.toml\":25,\"launch.json\":26,\"remappings.txt\":3,\"script/Deploy.s.sol\":38,\"src/BMCIlluminatiPass.sol\":550,\"src/LaunchToken.sol\":20,\"src/StandardArt.sol\":236,\"src/interfaces/IBMCToken.sol\":18,\"test/BMCIlluminatiPass.t.sol\":883,\"test/BMCMainnetFork.t.sol\":57,\"test/ChainlinkMockIntegration.t.sol\":155,\"test/LaunchToken.t.sol\":50,\"test/VRFRecovery.t.sol\":533,\"test/mocks/MockBMC.sol\":42,\"test/mocks/MockVRFCoordinator.sol\":72,\"website/app.js\":192,\"website/config.js\":11,\"website/index.html\":95,\"website/styles.css\":44},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4b406fccb6d5bdca1bb96f48fb197b07e0a835f551983667fc760fb7615696e9","verifiedTreeHash":"ce484c3a23368186f5cd98b0c253ff33b58272ef","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":7709,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.50s\nCompiler run successful!\n","passed":true},{"durationMs":746,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/BMCMainnetFork.t.sol:BMCMainnetForkTest\n[SKIP] test_RealBMCGenesisMint() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 454.53µs (90.83µs CPU time)\n\nRan 3 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsWholeSupplyToDeployer() (gas: 72397)\n[PASS] test_NoMintOrAdminSurface() (gas: 161035)\n[PASS] test_TransferMovesExactAmount() (gas: 93419)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.58ms (559.48µs CPU time)\n\nRan 5 tests for test/ChainlinkMockIntegration.t.sol:ChainlinkMockIntegrationTest\n[PASS] test_ExpiredRequestRecoveredThroughChainlinkMock() (gas: 1304112)\n[PASS] test_NotAConsumerReverts() (gas: 367218)\n[PASS] test_RequestAndFulfilThroughChainlinkMock() (gas: 779232)\n[PASS] test_SeveralRequestsOutOfOrderThroughChainlinkMock() (gas: 1950705)\n[PASS] test_UnfundedSubscriptionKeepsTokenPendingUntilFunded() (gas: 969068)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 21.50ms (4.53ms CPU time)\n\nRan 16 tests for test/VRFRecovery.t.sol:VRFRecoveryTest\n[PASS] testFuzz_RerequestedRevealAlwaysInInventory(uint256,uint32) (runs: 64, μ: 772072, ~: 770772)\n[PASS] test_DuplicateCallbackForNewRequestIsIgnored() (gas: 1030796)\n[PASS] test_ExpiredRequestBlocksQueueUntilRerequest() (gas: 2184794)\n[PASS] test_FrontRunningFulfilmentBeforeTimeoutFails() (gas: 861243)\n[PASS] test_FullMintOutWithExpiredRequestsPreservesInvariants() (gas: 273282819)\n[PASS] test_LateDeliveryForAbandonedRequestIsRejected() (gas: 963520)\n[PASS] test_OutOfOrderFulfilmentWithRerequestKeepsMintOrder() (gas: 3302442)\n[PASS] test_OwnerHasNoSelectiveRetryOrReroll() (gas: 1409444)\n[PASS] test_RerequestBeforeTimeoutRevertsExactlyAtBoundary() (gas: 636702)\n[PASS] test_RerequestBurnsNothingAndKeepsTokenIntact() (gas: 683488)\n[PASS] test_RerequestOutcomeIndependentOfCallerAndTiming() (gas: 1827152)\n[PASS] test_RerequestRevertsCleanlyWhenCoordinatorRejects() (gas: 755734)\n[PASS] test_RerequestRevertsUnlessTokenAwaitsRandomness() (gas: 1814978)\n[PASS] test_RerequestUsesCurrentConfigAfterLaneChange() (gas: 657426)\n[PASS] test_SecondRerequestNeedsAnotherFullTimeout() (gas: 1031951)\n[PASS] test_StoredUnfavourableWordCannotBeRerequested() (gas: 924795)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 458.35ms (489.41ms CPU time)\n\nRan 40 tests for test/BMCIlluminatiPass.t.sol:BMCIlluminatiPassTest\n[PASS] testFuzz_SingleRevealAlwaysInInventory(uint256) (runs: 64, μ: 10981526, ~: 10980475)\n[PASS] test_CallbackBatchIsGasAware() (gas: 12117171)\n[PASS] test_Constants() (gas: 82468)\n[PASS] test_DeployScriptConfig() (gas: 10593505)\n[PASS] test_DeployScriptRequiresOwner() (gas: 5673573)\n[PASS] test_ExcessAllowanceOnlyBurnsPrice() (gas: 341753)\n[PASS] test_FinalizationOrderCannotBeChosen() (gas: 11976948)\n[PASS] test_FirstMinterGetsLegendaryGenesis1() (gas: 2070355)\n[PASS] test_FulfillRevealsAndLocksMetadata() (gas: 17272738)\n[PASS] test_Full500MintSimulation() (gas: 278167273)\n[PASS] test_FullMintOutWithInOrderFulfillment() (gas: 244517071)\n[PASS] test_GenesisCIDIsConstantWithNoSetter() (gas: 1414317)\n[PASS] test_GenesisRevertsAfterPhase1() (gas: 10532826)\n[PASS] test_Mints2To40AreSequentialGenesis() (gas: 14428006)\n[PASS] test_MysteryBurnRevertsIfVRFRequestFails() (gas: 10666821)\n[PASS] test_MysteryRevertsDuringPhase1() (gas: 455835)\n[PASS] test_MysteryRevertsWhenVRFUnconfigured() (gas: 10520646)\n[PASS] test_NoOwnerMintOrWithdrawSurface() (gas: 81131)\n[PASS] test_OnlyCoordinatorCanFulfill() (gas: 10898309)\n[PASS] test_OutOfOrderFulfillmentFinalizesInMintOrder() (gas: 12199515)\n[PASS] test_OwnershipTransferIsTwoStep() (gas: 296032)\n[PASS] test_PendingMetadataIsNeutral() (gas: 13582598)\n[PASS] test_PermissionlessFinalizationWhenCallbackGasTooLow() (gas: 11065821)\n[PASS] test_Phase1CostsExactlyOneMillion() (gas: 390302)\n[PASS] test_Phase2CostsExactly350k() (gas: 10953731)\n[PASS] test_ReentrancyGenesisBlocked() (gas: 884690)\n[PASS] test_ReentrancyMysteryBlocked() (gas: 11367640)\n[PASS] test_RevertInsufficientAllowance() (gas: 179285)\n[PASS] test_RevertInsufficientBalance() (gas: 193157)\n[PASS] test_RevertNoAllowance() (gas: 144231)\n[PASS] test_RevertWhenTokenDecimalsUnexpected() (gas: 728429)\n[PASS] test_RevertWhenTokenDoesNotActuallyBurn() (gas: 744895)\n[PASS] test_StandardArtCombinationsUnique() (gas: 1482872)\n[PASS] test_StandardArtCoversEveryTrait() (gas: 743721)\n[PASS] test_StandardArtRendersWithoutGenesisTraits() (gas: 30070221)\n[PASS] test_StandardMetadataAfterReveal() (gas: 19961345)\n[PASS] test_TransfersAreStandard() (gas: 469656)\n[PASS] test_UnavailableRandomnessStaysPendingNoFallback() (gas: 11347945)\n[PASS] test_VRFConfigValidation() (gas: 253065)\n[PASS] test_ViewsRevertForNonexistent() (gas: 33595)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 539.83ms (1.78s CPU time)\n\nRan 5 test suites in 543.32ms (1.02s CPU time): 64 tests passed, 0 failed, 1 skipped (65 total tests)\n","passed":true},{"durationMs":1632,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BMCIlluminatiPass.acceptOwnership()\",\"BMCIlluminatiPass.approve(address,uint256)\",\"BMCIlluminatiPass.finalizeReveals(uint256)\",\"BMCIlluminatiPass.mintGenesis()\",\"BMCIlluminatiPass.mintMystery()\",\"BMCIlluminatiPass.rawFulfillRandomWords(uint256,uint256[])\",\"BMCIlluminatiPass.renounceOwnership()\",\"BMCIlluminatiPass.rerequestRandomness(uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256,bytes)\",\"BMCIlluminatiPass.setApprovalForAll(address,bool)\",\"BMCIlluminatiPass.setVRFConfig(uint256,bytes32,uint32,uint16,bool)\",\"BMCIlluminatiPass.transferFrom(address,address,uint256)\",\"BMCIlluminatiPass.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".github/workflows/main.yml\":43,\".gitignore\":5,\"ADAPTATION.md\":66,\"README.md\":187,\"artwork/genesis/genesis-01.png\":7910,\"artwork/genesis/genesis-02.png\":6998,\"artwork/genesis/genesis-03.png\":7016,\"artwork/genesis/genesis-04.png\":6664,\"artwork/genesis/genesis-05.png\":7186,\"artwork/genesis/genesis-06.png\":6703,\"artwork/genesis/genesis-07.png\":6488,\"artwork/genesis/genesis-08.png\":6807,\"artwork/genesis/genesis-09.png\":6793,\"artwork/genesis/genesis-10.png\":6550,\"artwork/genesis/genesis-11.png\":6125,\"artwork/genesis/genesis-12.png\":6418,\"artwork/genesis/genesis-13.png\":6298,\"artwork/genesis/genesis-14.png\":5909,\"artwork/genesis/genesis-15.png\":6142,\"artwork/genesis/genesis-16.png\":5727,\"artwork/genesis/genesis-17.png\":6019,\"artwork/genesis/genesis-18.png\":399,\"artwork/genesis/genesis-19.png\":369,\"artwork/genesis/genesis-20.png\":359,\"artwork/genesis/genesis-21.png\":330,\"artwork/genesis/genesis-22.png\":385,\"artwork/genesis/genesis-23.png\":391,\"artwork/genesis/genesis-24.png\":411,\"artwork/genesis/genesis-25.png\":393,\"artwork/genesis/genesis-26.png\":390,\"artwork/genesis/genesis-27.png\":386,\"artwork/genesis/genesis-28.png\":402,\"artwork/genesis/genesis-29.png\":375,\"artwork/genesis/genesis-30.png\":369,\"artwork/genesis/genesis-31.png\":421,\"artwork/genesis/genesis-32.png\":338,\"artwork/genesis/genesis-33.png\":389,\"artwork/genesis/genesis-34.png\":365,\"artwork/genesis/genesis-35.png\":352,\"artwork/genesis/genesis-36.png\":384,\"artwork/genesis/genesis-37.png\":365,\"artwork/genesis/genesis-38.png\":438,\"artwork/genesis/genesis-39.png\":374,\"artwork/genesis/genesis-40.png\":411,\"artwork/genesis/genesis-41.png\":380,\"artwork/genesis/genesis-42.png\":377,\"artwork/genesis/genesis-43.png\":399,\"artwork/genesis/genesis-44.png\":385,\"artwork/genesis/genesis-45.png\":352,\"artwork/genesis/genesis-46.png\":343,\"artwork/genesis/genesis-47.png\":362,\"artwork/genesis/genesis-48.png\":408,\"artwork/genesis/genesis-49.png\":325,\"artwork/genesis/genesis-50.png\":433,\"artwork/genesis/genesis-51.png\":377,\"artwork/genesis/genesis-52.png\":376,\"artwork/genesis/genesis-53.png\":401,\"artwork/genesis/genesis-54.png\":389,\"artwork/genesis/genesis-55.png\":361,\"artwork/genesis/genesis-56.png\":332,\"artwork/genesis/genesis-57.png\":414,\"artwork/genesis/genesis-58.png\":405,\"artwork/genesis/genesis-59.png\":409,\"artwork/genesis/genesis-60.png\":405,\"artwork/genesis/genesis-61.png\":369,\"artwork/genesis/genesis-62.png\":366,\"artwork/genesis/genesis-63.png\":379,\"artwork/genesis/genesis-64.png\":425,\"artwork/genesis/genesis-65.png\":406,\"artwork/genesis/genesis-66.png\":413,\"artwork/genesis/genesis-67.png\":350,\"artwork/genesis/genesis-68.png\":404,\"artwork/genesis/genesis-69.png\":392,\"artwork/genesis/genesis-70.png\":378,\"artwork/genesis/genesis-71.png\":401,\"artwork/genesis/genesis-72.png\":355,\"artwork/genesis/genesis-73.png\":405,\"artwork/genesis/genesis-74.png\":394,\"artwork/genesis/genesis-75.png\":390,\"artwork/genesis/genesis-76.png\":424,\"artwork/genesis/genesis-77.png\":381,\"artwork/tools/generate_genesis.py\":206,\"docs/PULL-REQUEST.md\":64,\"docs/SECURITY-REVIEW.md\":90,\"docs/TEST-RESULTS.md\":94,\"docs/VRF-RECOVERY-DESIGN.md\":131,\"foundry.toml\":25,\"remappings.txt\":3,\"script/Deploy.s.sol\":38,\"src/BMCIlluminatiPass.sol\":550,\"src/LaunchToken.sol\":20,\"src/StandardArt.sol\":236,\"src/interfaces/IBMCToken.sol\":18,\"test/BMCIlluminatiPass.t.sol\":883,\"test/BMCMainnetFork.t.sol\":57,\"test/ChainlinkMockIntegration.t.sol\":155,\"test/LaunchToken.t.sol\":50,\"test/VRFRecovery.t.sol\":533,\"test/mocks/MockBMC.sol\":42,\"test/mocks/MockVRFCoordinator.sol\":72,\"website/app.js\":192,\"website/config.js\":11,\"website/index.html\":95,\"website/styles.css\":44},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3760,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/BMCIlluminatiPass.sol:298: Reentrancy in BMCIlluminatiPass._burnExact(address,uint256) (src/BMCIlluminatiPass.sol#298-309):\n[medium/medium] reentrancy-no-eth at src/BMCIlluminatiPass.sol:227: Reentrancy in BMCIlluminatiPass.mintGenesis() (src/BMCIlluminatiPass.sol#227-240):\n[medium/medium] reentrancy-no-eth at src/BMCIlluminatiPass.sol:243: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#243-258):\n[medium/medium] unused-return at src/BMCIlluminatiPass.sol:318: BMCIlluminatiPass.rawFulfillRandomWords(uint256,uint256[]) (src/BMCIlluminatiPass.sol#318-339) ignores return value by this.finalizeReveals(batch) (src/BMCIlluminatiPass.sol#338)\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:280: Reentrancy in BMCIlluminatiPass._openRequest(uint256,BMCIlluminatiPass.VRFConfig) (src/BMCIlluminatiPass.sol#280-295):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:227: Reentrancy in BMCIlluminatiPass.mintGenesis() (src/BMCIlluminatiPass.sol#227-240):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:298: Reentrancy in BMCIlluminatiPass._burnExact(address,uint256) (src/BMCIlluminatiPass.sol#298-309):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:243: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#243-258):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:243: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#243-258):\n[low/medium] timestamp at src/BMCIlluminatiPass.sol:267: BMCIlluminatiPass.rerequestRandomness(uint256) (src/BMCIlluminatiPass.sol#267-277) uses timestamp for comparisons","passed":true},{"durationMs":848,"exitCode":0,"name":"aderyn","output":"[high] storage-array-memory-edit at src/BMCIlluminatiPass.sol:275: Storage Array Edited with Memory\n[low] centralization-risk at src/BMCIlluminatiPass.sol:45: Centralization Risk (2 places)\n[low] costly-loop at src/BMCIlluminatiPass.sol:347: Costly operations inside loop\n[low] internal-function-used-once at src/StandardArt.sol:59: Internal Function Used Only Once (5 places)\n[low] large-numeric-literal at src/BMCIlluminatiPass.sol:88: Large Numeric Literal (8 places)\n[low] literal-instead-of-constant at src/StandardArt.sol:36: Literal Instead of Constant (27 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"76c79dbd9ee234f8a00671100d8f54fe8f125785437d86da9d7e23fd1db666a9","verifiedTreeHash":"a1b1510726012537110b19c1292ee63c6dc945e8","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":3703,"exitCode":0,"name":"build","output":"Compiling 69 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.62s\nCompiler run successful!\n","passed":true},{"durationMs":10010,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/BMCMainnetFork.t.sol:BMCMainnetForkTest\n[SKIP] test_RealBMCGenesisMint() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 228.94µs (80.40µs CPU time)\n\nRan 3 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsWholeSupplyToDeployer() (gas: 72397)\n[PASS] test_NoMintOrAdminSurface() (gas: 161035)\n[PASS] test_TransferMovesExactAmount() (gas: 93419)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 690.48µs (393.04µs CPU time)\n\nRan 5 tests for test/ChainlinkMockIntegration.t.sol:ChainlinkMockIntegrationTest\n[PASS] test_ExpiredRequestRecoveredThroughChainlinkMock() (gas: 1304112)\n[PASS] test_NotAConsumerReverts() (gas: 367218)\n[PASS] test_RequestAndFulfilThroughChainlinkMock() (gas: 779232)\n[PASS] test_SeveralRequestsOutOfOrderThroughChainlinkMock() (gas: 1950705)\n[PASS] test_UnfundedSubscriptionKeepsTokenPendingUntilFunded() (gas: 969068)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 5.96ms (2.16ms CPU time)\n\nRan 17 tests for test/VRFRecoveryEdges.t.sol:VRFRecoveryEdgesTest\n[PASS] testFuzz_CallbackNeverRevertsAtAnyAllowedGas(uint256,bool) (runs: 300, μ: 643475, ~: 640331)\nLogs:\n  Bound result 2378673\n\n[PASS] testFuzz_OutcomesIndependentOfRecoveryHistory(uint256) (runs: 200, μ: 5282519, ~: 5255416)\n[PASS] testFuzz_RerequestGateIsExactlyTheTimeout(uint256,uint256) (runs: 500, μ: 525775, ~: 473560)\nLogs:\n  Bound result 814476\n  Bound result 43111\n\n[PASS] test_CallbackStoresWordAtMinimumConfigurableGas() (gas: 728831)\n[PASS] test_DeliveryForAnotherTokensRequestCannotTargetThisToken() (gas: 974986)\n[PASS] test_EveryExpiredTokenNeedsItsOwnRerequest() (gas: 2301994)\n[PASS] test_ExtraRandomWordsOnlyFirstIsUsed() (gas: 767254)\n[PASS] test_FailedCallbackIsRecoverableOnlyAfterTimeout() (gas: 946308)\n[PASS] test_FinalizeBatchingDoesNotChangeOutcomes() (gas: 8992736)\n[PASS] test_FinalizeWithZeroAndHugeMaxCount() (gas: 1255285)\n[PASS] test_InlineFinalizeOutOfGasKeepsStoredWordAndSameOutcome() (gas: 941027)\n[PASS] test_PendingMetadataUnchangedByRerequestAndLockedAfterReveal() (gas: 1753502)\n[PASS] test_ReceiverCannotRerequestAnotherExpiredTokenDuringMint() (gas: 1550481)\n[PASS] test_ReceiverCannotRerequestOwnTokenDuringMint() (gas: 958865)\n[PASS] test_ReceiverFinalizingDuringMintIsHarmless() (gas: 1582809)\n[PASS] test_RequestIdZeroIsNeverAccepted() (gas: 467487)\n[PASS] test_RerequestedTokenKeepsQueuePositionAndNeighboursOutcomes() (gas: 2463746)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 111.82ms (170.31ms CPU time)\n\nRan 16 tests for test/VRFRecovery.t.sol:VRFRecoveryTest\n[PASS] testFuzz_RerequestedRevealAlwaysInInventory(uint256,uint32) (runs: 64, μ: 772667, ~: 770796)\n[PASS] test_DuplicateCallbackForNewRequestIsIgnored() (gas: 1030796)\n[PASS] test_ExpiredRequestBlocksQueueUntilRerequest() (gas: 2184794)\n[PASS] test_FrontRunningFulfilmentBeforeTimeoutFails() (gas: 861243)\n[PASS] test_FullMintOutWithExpiredRequestsPreservesInvariants() (gas: 273282819)\n[PASS] test_LateDeliveryForAbandonedRequestIsRejected() (gas: 963520)\n[PASS] test_OutOfOrderFulfilmentWithRerequestKeepsMintOrder() (gas: 3302442)\n[PASS] test_OwnerHasNoSelectiveRetryOrReroll() (gas: 1409444)\n[PASS] test_RerequestBeforeTimeoutRevertsExactlyAtBoundary() (gas: 636702)\n[PASS] test_RerequestBurnsNothingAndKeepsTokenIntact() (gas: 683488)\n[PASS] test_RerequestOutcomeIndependentOfCallerAndTiming() (gas: 1827152)\n[PASS] test_RerequestRevertsCleanlyWhenCoordinatorRejects() (gas: 755734)\n[PASS] test_RerequestRevertsUnlessTokenAwaitsRandomness() (gas: 1814978)\n[PASS] test_RerequestUsesCurrentConfigAfterLaneChange() (gas: 657426)\n[PASS] test_SecondRerequestNeedsAnotherFullTimeout() (gas: 1031951)\n[PASS] test_StoredUnfavourableWordCannotBeRerequested() (gas: 924795)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 183.56ms (201.17ms CPU time)\n\nRan 40 tests for test/BMCIlluminatiPass.t.sol:BMCIlluminatiPassTest\n[PASS] testFuzz_SingleRevealAlwaysInInventory(uint256) (runs: 64, μ: 10981175, ~: 10980481)\n[PASS] test_CallbackBatchIsGasAware() (gas: 12117171)\n[PASS] test_Constants() (gas: 82468)\n[PASS] test_DeployScriptConfig() (gas: 10593505)\n[PASS] test_DeployScriptRequiresOwner() (gas: 5673573)\n[PASS] test_ExcessAllowanceOnlyBurnsPrice() (gas: 341753)\n[PASS] test_FinalizationOrderCannotBeChosen() (gas: 11976948)\n[PASS] test_FirstMinterGetsLegendaryGenesis1() (gas: 2070355)\n[PASS] test_FulfillRevealsAndLocksMetadata() (gas: 17272738)\n[PASS] test_Full500MintSimulation() (gas: 278167273)\n[PASS] test_FullMintOutWithInOrderFulfillment() (gas: 244517071)\n[PASS] test_GenesisCIDIsConstantWithNoSetter() (gas: 1414317)\n[PASS] test_GenesisRevertsAfterPhase1() (gas: 10532826)\n[PASS] test_Mints2To40AreSequentialGenesis() (gas: 14428006)\n[PASS] test_MysteryBurnRevertsIfVRFRequestFails() (gas: 10666821)\n[PASS] test_MysteryRevertsDuringPhase1() (gas: 455835)\n[PASS] test_MysteryRevertsWhenVRFUnconfigured() (gas: 10520646)\n[PASS] test_NoOwnerMintOrWithdrawSurface() (gas: 81131)\n[PASS] test_OnlyCoordinatorCanFulfill() (gas: 10898309)\n[PASS] test_OutOfOrderFulfillmentFinalizesInMintOrder() (gas: 12199515)\n[PASS] test_OwnershipTransferIsTwoStep() (gas: 296032)\n[PASS] test_PendingMetadataIsNeutral() (gas: 13582598)\n[PASS] test_PermissionlessFinalizationWhenCallbackGasTooLow() (gas: 11065821)\n[PASS] test_Phase1CostsExactlyOneMillion() (gas: 390302)\n[PASS] test_Phase2CostsExactly350k() (gas: 10953731)\n[PASS] test_ReentrancyGenesisBlocked() (gas: 884690)\n[PASS] test_ReentrancyMysteryBlocked() (gas: 11367640)\n[PASS] test_RevertInsufficientAllowance() (gas: 179285)\n[PASS] test_RevertInsufficientBalance() (gas: 193157)\n[PASS] test_RevertNoAllowance() (gas: 144231)\n[PASS] test_RevertWhenTokenDecimalsUnexpected() (gas: 728429)\n[PASS] test_RevertWhenTokenDoesNotActuallyBurn() (gas: 744895)\n[PASS] test_StandardArtCombinationsUnique() (gas: 1482872)\n[PASS] test_StandardArtCoversEveryTrait() (gas: 743721)\n[PASS] test_StandardArtRendersWithoutGenesisTraits() (gas: 30070221)\n[PASS] test_StandardMetadataAfterReveal() (gas: 19961345)\n[PASS] test_TransfersAreStandard() (gas: 469656)\n[PASS] test_UnavailableRandomnessStaysPendingNoFallback() (gas: 11347945)\n[PASS] test_VRFConfigValidation() (gas: 253065)\n[PASS] test_ViewsRevertForNonexistent() (gas: 33595)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 219.36ms (829.55ms CPU time)\n\nRan 1 test for test/BMCIlluminatiPass.invariant.t.sol:BMCIlluminatiPassInvariantTest\n[PASS]\nBMCIlluminatiPassInvariantTest invariants:\n[PASS] invariant_BurnConservation\n[PASS] invariant_GenesisTokensFixedForever\n[PASS] invariant_RequestMappingsConsistent\n[PASS] invariant_RevealCountsAndPool\n[PASS] invariant_RevealedOutcomesImmutable\n[PASS] invariant_StrictMintOrderReveal\n[PASS] invariant_SupplyCapsAndPhases\n[PASS] invariant_WordsAreNeverDiscarded\n BMCIlluminatiPassInvariantTest invariants (runs: 48, calls: 5760, reverts: 0)\n\n╭-------------+---------------------+-------+---------+----------╮\n| Contract    | Selector            | Calls | Reverts | Discards |\n+================================================================+\n| PassHandler | deliverAbandoned    | 447   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | deliverUnknown      | 449   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | finalize            | 440   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | fulfillAsOutsider   | 409   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | fulfillLive         | 903   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | mintGenesis         | 469   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | mintMystery         | 443   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | rerequest           | 486   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | setConfig           | 448   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | setConfigAsNonOwner | 455   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | transfer            | 422   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| PassHandler | warp                | 389   | 0       | 0        |\n╰-------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 868\n  Bound result 24149\n  Bound result 77\n  Bound result 777784\n  Bound result 179\n  Bound result 5\n  Bound result 268848\n  Bound result 95000\n  Bound result 104\n  Bound result 190\n  Bound result 2360331\n  Bound result 108\n  Bound result 20\n  Bound result 20\n  Bound result 13\n  Bound result 263\n  Bound result 9\n  Bound result 14645\n  Bound result 2352219\n  Bound result 12\n  Bound result 6\n  Bound result 27\n  Bound result 2358598\n  Bound result 192\n  Bound result 11\n  Bound result 0\n  Bound result 3\n  Bound result 13483\n  Bound result 1579\n  Bound result 869\n  Bound result 405\n  Bound result 11\n  Bound result 16\n  Bound result 3714247999\n  Bound result 2365709\n  Bound result 16\n  Bound result 14\n  Bound result 715\n  Bound result 165044\n  Bound result 7\n  Bound result 33805\n  Bound result 1000\n  Bound result 2360362\n  Bound result 26\n  Bound result 0\n  Bound result 7\n  Bound result 422\n  Bound result 8871\n  Bound result 350000\n  Bound result 50\n  Bound result 1\n  Bound result 288000\n  Bound result 528071654\n  Bound result 2350257\n  Bound result 193\n  Bound result 3\n  Bound result 0\n  Bound result 1878\n  Bound result 964\n  Bound result 19\n  Bound result 4\n  Bound result 37\n  Bound result 2358178\n  Bound result 96\n  Bound result 11\n  Bound result 4\n  Bound result 7\n  Bound result 8394\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.92s (9.91s CPU time)\n\nRan 7 test suites in 9.92s (10.44s CPU time): 82 tests passed, 0 failed, 1 skipped (83 total tests)\n","passed":true},{"durationMs":922,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BMCIlluminatiPass.acceptOwnership()\",\"BMCIlluminatiPass.approve(address,uint256)\",\"BMCIlluminatiPass.finalizeReveals(uint256)\",\"BMCIlluminatiPass.mintGenesis()\",\"BMCIlluminatiPass.mintMystery()\",\"BMCIlluminatiPass.rawFulfillRandomWords(uint256,uint256[])\",\"BMCIlluminatiPass.renounceOwnership()\",\"BMCIlluminatiPass.rerequestRandomness(uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256)\",\"BMCIlluminatiPass.safeTransferFrom(address,address,uint256,bytes)\",\"BMCIlluminatiPass.setApprovalForAll(address,bool)\",\"BMCIlluminatiPass.setVRFConfig(uint256,bytes32,uint32,uint16,bool)\",\"BMCIlluminatiPass.transferFrom(address,address,uint256)\",\"BMCIlluminatiPass.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".github/workflows/main.yml\":43,\".gitignore\":5,\"ADAPTATION.md\":66,\"README.md\":187,\"artwork/genesis/genesis-01.png\":7910,\"artwork/genesis/genesis-02.png\":6998,\"artwork/genesis/genesis-03.png\":7016,\"artwork/genesis/genesis-04.png\":6664,\"artwork/genesis/genesis-05.png\":7186,\"artwork/genesis/genesis-06.png\":6703,\"artwork/genesis/genesis-07.png\":6488,\"artwork/genesis/genesis-08.png\":6807,\"artwork/genesis/genesis-09.png\":6793,\"artwork/genesis/genesis-10.png\":6550,\"artwork/genesis/genesis-11.png\":6125,\"artwork/genesis/genesis-12.png\":6418,\"artwork/genesis/genesis-13.png\":6298,\"artwork/genesis/genesis-14.png\":5909,\"artwork/genesis/genesis-15.png\":6142,\"artwork/genesis/genesis-16.png\":5727,\"artwork/genesis/genesis-17.png\":6019,\"artwork/genesis/genesis-18.png\":399,\"artwork/genesis/genesis-19.png\":369,\"artwork/genesis/genesis-20.png\":359,\"artwork/genesis/genesis-21.png\":330,\"artwork/genesis/genesis-22.png\":385,\"artwork/genesis/genesis-23.png\":391,\"artwork/genesis/genesis-24.png\":411,\"artwork/genesis/genesis-25.png\":393,\"artwork/genesis/genesis-26.png\":390,\"artwork/genesis/genesis-27.png\":386,\"artwork/genesis/genesis-28.png\":402,\"artwork/genesis/genesis-29.png\":375,\"artwork/genesis/genesis-30.png\":369,\"artwork/genesis/genesis-31.png\":421,\"artwork/genesis/genesis-32.png\":338,\"artwork/genesis/genesis-33.png\":389,\"artwork/genesis/genesis-34.png\":365,\"artwork/genesis/genesis-35.png\":352,\"artwork/genesis/genesis-36.png\":384,\"artwork/genesis/genesis-37.png\":365,\"artwork/genesis/genesis-38.png\":438,\"artwork/genesis/genesis-39.png\":374,\"artwork/genesis/genesis-40.png\":411,\"artwork/genesis/genesis-41.png\":380,\"artwork/genesis/genesis-42.png\":377,\"artwork/genesis/genesis-43.png\":399,\"artwork/genesis/genesis-44.png\":385,\"artwork/genesis/genesis-45.png\":352,\"artwork/genesis/genesis-46.png\":343,\"artwork/genesis/genesis-47.png\":362,\"artwork/genesis/genesis-48.png\":408,\"artwork/genesis/genesis-49.png\":325,\"artwork/genesis/genesis-50.png\":433,\"artwork/genesis/genesis-51.png\":377,\"artwork/genesis/genesis-52.png\":376,\"artwork/genesis/genesis-53.png\":401,\"artwork/genesis/genesis-54.png\":389,\"artwork/genesis/genesis-55.png\":361,\"artwork/genesis/genesis-56.png\":332,\"artwork/genesis/genesis-57.png\":414,\"artwork/genesis/genesis-58.png\":405,\"artwork/genesis/genesis-59.png\":409,\"artwork/genesis/genesis-60.png\":405,\"artwork/genesis/genesis-61.png\":369,\"artwork/genesis/genesis-62.png\":366,\"artwork/genesis/genesis-63.png\":379,\"artwork/genesis/genesis-64.png\":425,\"artwork/genesis/genesis-65.png\":406,\"artwork/genesis/genesis-66.png\":413,\"artwork/genesis/genesis-67.png\":350,\"artwork/genesis/genesis-68.png\":404,\"artwork/genesis/genesis-69.png\":392,\"artwork/genesis/genesis-70.png\":378,\"artwork/genesis/genesis-71.png\":401,\"artwork/genesis/genesis-72.png\":355,\"artwork/genesis/genesis-73.png\":405,\"artwork/genesis/genesis-74.png\":394,\"artwork/genesis/genesis-75.png\":390,\"artwork/genesis/genesis-76.png\":424,\"artwork/genesis/genesis-77.png\":381,\"artwork/tools/generate_genesis.py\":206,\"docs/PULL-REQUEST.md\":64,\"docs/SECURITY-REVIEW.md\":90,\"docs/TEST-RESULTS.md\":94,\"docs/VRF-RECOVERY-DESIGN.md\":131,\"foundry.toml\":25,\"remappings.txt\":3,\"script/Deploy.s.sol\":38,\"src/BMCIlluminatiPass.sol\":550,\"src/LaunchToken.sol\":20,\"src/StandardArt.sol\":236,\"src/interfaces/IBMCToken.sol\":18,\"test/BMCIlluminatiPass.invariant.t.sol\":572,\"test/BMCIlluminatiPass.t.sol\":883,\"test/BMCMainnetFork.t.sol\":57,\"test/ChainlinkMockIntegration.t.sol\":155,\"test/LaunchToken.t.sol\":50,\"test/VRFRecovery.t.sol\":533,\"test/VRFRecoveryEdges.t.sol\":491,\"test/mocks/MockBMC.sol\":42,\"test/mocks/MockVRFCoordinator.sol\":72,\"website/app.js\":192,\"website/config.js\":11,\"website/index.html\":95,\"website/styles.css\":44},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"deda0a2372754ca88e3962317005225a5f5e71d846b1b40c0ef0823153b07f44","verifiedTreeHash":"b15c7e62ca43c89078143732651ba19899862873","verifierVersion":"0.1.0+fb8278c5"}]}