{"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":"083fb0b0-099f-4b6e-93f7-1a8db6b3bf03","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"00ff9bf19e261f4acf2fe665cd7cf6b36686edda899e4c613fdfab4aefd0844e","dependsOn":["audit_imported_code"],"execution":{"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":null,"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":"working"},{"acceptedSubmissionHash":null,"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":"working"},{"acceptedSubmissionHash":"87618a78ea917c0dcc0f56b9b5ef8e947d0653f4038ecb0d99d6df7ec2f51b7a","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":"waiting"},{"acceptedSubmissionHash":null,"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":"working"},{"acceptedSubmissionHash":null,"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":"working"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":null,"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":"working"}],"objective":"PROJECT: BMC ILLUMINATI PASS #1356 — FINAL VRF ARCHITECTURE SECURITY REVIEW\n\nRepository:\nhttps://github.com/0xSt3w/launch-1356-project-bmc-illuminati-pass\n\nBranch: security/vrf-recovery\n\nPrevious audit:\nhttps://github.com/Identity-md/research/blob/main/jobs/8e2c8de5-58ed-4243-bd09-bfa32e0eafde/files/AUDIT.md\n\nTASK:\n\nPerform an independent security and fairness review of the two Chainlink VRF architectures:\n\nA. Existing 460-request model with strict mint-order finalization.\n\nB. Experimental single-seed deterministic distribution model in src/experimental/BMCPassVRFPrototype.sol.\n\nReview the associated Foundry tests, including VRFRecovery, VRFSafetyProperties and BMCPassVRFChainlinkIntegration.\n\nDetermine whether either architecture can safely handle an expired or permanently unavailable VRF request without introducing exploitable rerolls, owner manipulation, selective fulfillment or rarity bias.\n\nIf neither architecture satisfies these requirements, explicitly report that conclusion and recommend a safer design.\n\nPreserve:\n- 500 NFT maximum\n- 77 Genesis, including Legendary #1\n- 423 Standard\n- 40 sequential initial Genesis\n- 460 Mystery\n- Genesis mint price: 1,000,000 BMC\n- Mystery mint price: 350,000 BMC\n- Permanent BMC burning\n\nDELIVERABLES:\n\n1. Security comparison of both architectures.\n2. Concrete attack scenarios and proof-of-concept tests.\n3. Analysis of randomness availability, liveness and fairness.\n4. Recommended architecture and residual risks.\n5. Required code changes and test coverage.\n6. Mainnet readiness assessment.\n\nSTRICT RESTRICTIONS:\n\nDO NOT deploy on Ethereum Mainnet.\nDO NOT broadcast transactions.\nDO NOT transfer or burn real BMC.\nDO NOT request wallet approvals.\nDO NOT modify production contracts.\nDO NOT merge pull requests.\nDO NOT publish a new website.\n\nPerform the free feasibility check first, disclose any costs and wait for explicit approval before paid work.","parentJobId":null,"planHash":"9526741ea21052d88becd4166d513bae4fe7d87678b40a3b5717c991a51c6866","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"083fb0b0-099f-4b6e-93f7-1a8db6b3bf03","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":"51511","feedbackHash":"eb8bd0918f50b0c9b8a2bc2c2f21721fb7978f0926cbe214dfef650cc95a5fd1","nodeKey":"adapt_contract_project","submissionHash":"00ff9bf19e261f4acf2fe665cd7cf6b36686edda899e4c613fdfab4aefd0844e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52190","feedbackHash":"dba49807fcc9710404114532c0524cc593202d542e0fc95fbbdefe770e8c2513","nodeKey":"audit_imported_code","submissionHash":"87618a78ea917c0dcc0f56b9b5ef8e947d0653f4038ecb0d99d6df7ec2f51b7a","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"80f67a5045841e880ceb51611978fce6bcbd6bc3a9625ec5e97d9a122c0da862","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"552769ce92f849b6520807f40cdcf35b5de85a243654fe668f3f041e63af5ee2","device":"fbcdfc017217af1f","findings":[],"hash":"00ff9bf19e261f4acf2fe665cd7cf6b36686edda899e4c613fdfab4aefd0844e","nodeId":"b778decd-2656-46cd-a839-c75f51e70867","outcome":"completed","summary":"The work is complete and verified. Build passes, and the suite reports 73 passed, 0 failed, 1 skipped (the mainnet fork test, which needs an RPC).\n\n**Review verdict.** Neither architecture as it arrived handles an expired or lost VRF request. In the production contract a lost request froze that token and every later one forever, and the audit's proof test fails on the original code. The prototype is only a pure function; its sole consumer allowed exactly one request, so one lost request froze all 460 tokens. The full comparison, attack scenarios, liveness and fairness analysis, recommendation and readiness assessment are in `docs/VRF-ARCHITECTURE-REVIEW.md`.\n\n**Recommendation.** Keep architecture A (per-mint requests, strict mint order) with a permissionless timed re-request, which I implemented. Architecture B is fairer on paper but needs a rule-based sale close that the brief does not define, and it would delay every reveal to sell-out. Residual risks are documented: the VRF node could withhold, and the subscription owner can delay but not choose outcomes.\n\n**Code changes in `src/BMCIlluminatiPass.sol`:**\n- New `rerequestRandomness(tokenId)`: anyone may open a replacement request once a token has waited 7 days with no word. Every request ever issued stays valid and the first delivered word wins, so no re-request can discard a word or reroll.\n- Constructor now takes the VRF configuration, so the contract is usable at deployment with no owner call. The owner keeps `setVRFConfig` as a disclosed power.\n- Genesis CID folded into a constant. I verified the CID from the fork test on a public gateway: genesis-01 and genesis-77 match the repository artwork byte for byte.\n- Reveal gas budget raised to cover the measured cost of a cold reveal.\n\n**Tests.** The recovery suite is rewritten so it fails on the freeze and passes on the fix: lost earlier request, failed callback, first-delivered-wins in both orders, rate limiting, owner has no shortcut, five unfunded tokens recovered after funding, minimum callback gas through Chainlink's mock, and a fuzzed delivery order over six pending tokens. The prototype's test consumer now refuses a seed while minting is open and supports a timed re-request, with a proof-of-concept for position targeting. The fork test skips cleanly without an RPC.\n\n**Not changed.** Odds drift with reveal state is inherent to architecture A and is now disclosed in the README rather than removed. The audit's literal proof expects later tokens to reveal with no new randomness for the stuck one, which strict mint order cannot do; the same scenario with the re-request step is covered by a test.\n\n**Open before deployment** (listed in the review): an independent audit of this adaptation, the live BMC burnFrom fork check, a funded subscription whose values go into the constructor, and a gas check under the real coordinator. `ADAPTATION.md` lists all 17 changes with the finding or rule behind each.","treeHash":"ae0daee3b29652de2811161c40278399c764f7df","usage":{"cachedInputTokens":3064946,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":82657,"runtime":"claude","turns":85,"wallClockMs":1038108}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ce6eaff570c608ab","findings":[],"hash":"222a43e8ec7c4e96287533d17ae517365d7584f6ec5ca7087402809a832a4dce","nodeId":"19fece39-d3d8-4450-b06b-002191e23134","outcome":"failed","summary":"needs input: The accepted BMCIlluminatiPass constructor takes the requester's Chainlink VRF v2.5 subscription id (uint256, rejected when 0), the gas-lane key hash (bytes32, rejected when 0) and the subscription's payment mode (nativePayment bool). None of these is in the repository or the brief: the README lists the VRF subscription as 'Blocker until created' and says the owner chooses the gas lane. A placeholder is impossible because the constructor validates the values and a made-up subscription id would point the contract at a subscription it is not a consumer of. Everything else is fixed: owner $owner, BMC 0x38224239f2BDae357D47cb864BC533d7f72dF961, coordinator 0xD7f86b4b8Cae7D942340FF628F82735b7a20893a, callbackGasLimit 500000 and requestConfirmations 3 (README recommended values). — What are the mainnet Chainlink VRF v2.5 subscription id (the number shown at vrf.chain.link) and the key hash (gas lane) you want BMCIlluminatiPass to use, and is that subscription funded with LINK (nativePayment=","treeHash":null,"usage":{"cachedInputTokens":554723,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":4731,"runtime":"claude","turns":11,"wallClockMs":94528}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3beb94a990617d46","findings":[{"citation":"resolved","description":"finalizeReveals only advances while token GENESIS_PHASE_SUPPLY+revealedCount+1 is in RandomnessStored. A token whose request is never fulfilled stays AwaitingRandomness forever, and every token minted after it can never be revealed even when its own random word was delivered and stored. Chainlink VRF v2.5 makes this state reachable without any attacker: (1) the docs state that requests on an under-funded subscription 'remain pending for up to 24 hours before they expire' (docs.chain.link/vrf/v2-5/overview/subscription), and requestRandomWords is accepted on-chain regardless of balance, so a subscription that runs dry overnight during the 460-mint sale expires every request made in that window; (2) a callback that fails is still marked fulfilled by the coordinator with the word discarded (the vendored VRFCoordinatorV2_5Mock mirrors this: delete s_requests + RandomWordsFulfilled(success=false)); (3) coordinator decommissioning. There is no re-request, no expiry handling and no owner power that can repair it: setVRFConfig only affects future requests. Up to 459 holders who each burned 350,000 BMC would hold permanently pending tokens, and the 37 mystery Genesis would never be distributed. The subscription owner (normally the project owner) can also trigger this at will by simply not funding the subscription for 24h after a mint, which is an undisclosed owner power to freeze the collection. The existing tests testMissingEarlierRequestBlocksLaterReveal, testDelayedPredecessorDoesNotBurnAgainOrChangeSupply and testLaterRandomnessStoredUntilEarlierRequestArrives assert this freeze as expected behaviour instead of treating it as a defect.","line":321,"path":"src/BMCIlluminatiPass.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC20Burnable} from \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol\";\nimport {VRFCoordinatorV2_5Mock} from \"@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol\";\nimport {BMCIlluminatiPass} from \"src/BMCIlluminatiPass.sol\";\n\n/// @dev 18-decimal ERC20Burnable stand-in for BMC (same semantics as the project's MockBMC).\ncontract ProofBMC is ERC20, ERC20Burnable {\n    constructor() ERC20(\"BMC Illuminati\", \"BMC\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice Proof: one VRF request that is never fulfilled (Chainlink v2.5 drops requests that stay\n///         unfunded for 24h; a callback that fails is marked fulfilled with the word lost) freezes\n///         every later token forever, including tokens whose own random word was already delivered\n///         and stored. The contract offers no recovery path and the owner cannot repair it.\n///\n///         Fails on the current code: tokens 42 and 43 have RandomnessStored but can never be Revealed.\n///         Passes once the design no longer makes a token's reveal depend on an earlier token's\n///         request (expiry/re-request handling, an order-independent draw, or a single-seed design).\ncontract HeadOfLineBlockingProof is Test {\n    ProofBMC bmc;\n    VRFCoordinatorV2_5Mock coordinator;\n    BMCIlluminatiPass pass;\n    uint256 subId;\n\n    address owner = makeAddr(\"owner\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n    address carol = makeAddr(\"carol\");\n\n    function setUp() public {\n        bmc = new ProofBMC();\n        coordinator = new VRFCoordinatorV2_5Mock(0.1 ether, 1e9, 4e15);\n        pass = new BMCIlluminatiPass(owner, address(bmc), address(coordinator));\n        subId = coordinator.createSubscription();\n        coordinator.addConsumer(subId, address(pass));\n        coordinator.fundSubscription(subId, 100 ether);\n        vm.startPrank(owner);\n        pass.setGenesisImagesCID(\"bafyproof\");\n        pass.setVRFConfig(subId, keccak256(\"lane\"), 500_000, 3, false);\n        vm.stopPrank();\n        for (uint256 i = 0; i < 40; ++i) {\n            address minter = address(uint160(0x7000 + i));\n            bmc.mint(minter, 1_000_000e18);\n            vm.startPrank(minter);\n            bmc.approve(address(pass), 1_000_000e18);\n            pass.mintGenesis();\n            vm.stopPrank();\n        }\n    }\n\n    function _mint(address minter) internal returns (uint256 id, uint256 req) {\n        bmc.mint(minter, 350_000e18);\n        vm.startPrank(minter);\n        bmc.approve(address(pass), 350_000e18);\n        (id, req) = pass.mintMystery();\n        vm.stopPrank();\n    }\n\n    function test_LostEarlierRequestMustNotFreezeLaterDeliveredTokens() public {\n        (uint256 id41,) = _mint(alice); // request for 41 is never fulfilled (expired / lost)\n        (uint256 id42, uint256 req42) = _mint(bob);\n        (uint256 id43, uint256 req43) = _mint(carol);\n        assertEq(id41, 41);\n        assertEq(id42, 42);\n        assertEq(id43, 43);\n\n        // Chainlink delivers 42 and 43 normally.\n        uint256[] memory words = new uint256[](1);\n        words[0] = 0xB0B;\n        coordinator.fulfillRandomWordsWithOverride(req42, address(pass), words);\n        words[0] = 0xCA201;\n        coordinator.fulfillRandomWordsWithOverride(req43, address(pass), words);\n        assertEq(uint8(pass.revealStatusOf(id42)), uint8(BMCIlluminatiPass.RevealStatus.RandomnessStored));\n        assertEq(uint8(pass.revealStatusOf(id43)), uint8(BMCIlluminatiPass.RevealStatus.RandomnessStored));\n\n        // Far beyond Chainlink's 24h pending window: request 41 is gone for good.\n        vm.warp(block.timestamp + 30 days);\n        vm.roll(block.number + 216_000);\n\n        // Nothing the owner can do touches token 41 or the queue.\n        vm.prank(owner);\n        pass.setVRFConfig(subId, keccak256(\"other-lane\"), 2_500_000, 3, false);\n\n        // Expected: holders whose randomness was delivered get their reveal.\n        // Actual on current code: finalizeReveals returns 0 forever; 42 and 43 stay Pending.\n        pass.finalizeReveals(10);\n        assertEq(\n            uint8(pass.revealStatusOf(id42)),\n            uint8(BMCIlluminatiPass.RevealStatus.Revealed),\n            \"token 42 with delivered randomness is frozen behind lost request 41\"\n        );\n        assertEq(\n            uint8(pass.revealStatusOf(id43)),\n            uint8(BMCIlluminatiPass.RevealStatus.Revealed),\n            \"token 43 with delivered randomness is frozen behind lost request 41\"\n        );\n        assertTrue(pass.tierOf(id42) != BMCIlluminatiPass.Tier.Pending);\n        assertTrue(pass.tierOf(id43) != BMCIlluminatiPass.Tier.Pending);\n    }\n}","reproduction":"State: phase 1 complete (40 Genesis minted), subscription funded, VRF config set. Alice mints token 41 (request R41), Bob mints 42 (R42), Carol mints 43 (R43). The coordinator delivers R42 (word 0xB0B) and R43 (word 0xCA201); both tokens become RandomnessStored. R41 is never delivered (expired after >24h unfunded, or callback marked failed). Warp 30 days, owner calls setVRFConfig with any values. Call finalizeReveals(10). Expected: tokens 42 and 43, whose randomness is on-chain, are revealed. Actual: finalizeReveals returns 0 forever, revealStatusOf(42)==RandomnessStored (2), tierOf(42)==Pending, and the same for every later token; the collection can never complete.","severity":"high","snippet":"        while (done < maxCount && tokenId <= totalMinted && _revealStatus[tokenId] == RevealStatus.RandomnessStored) {","title":"Strict mint-order finalization turns one lost VRF request into a permanent freeze of every later token (head-of-line blocking)"},{"citation":"resolved","description":"The stated liveness argument ('requests remain pending while the subscription is unfunded and are fulfilled once it is funded again') is false for Chainlink VRF v2.5: the official docs say pending requests on an under-funded subscription expire after 24 hours. Because requestRandomWords does not check the subscription balance, mintMystery keeps accepting burns and opening requests while the subscription is empty; every such request older than 24h is lost, and the affected token (plus, per the high finding, every later token) is permanently pending. The only on-chain signal that a request is still open is the coordinator's pendingRequestExists(subId), which cannot distinguish 'still to be fulfilled' from 'expired off-chain', so the contract cannot detect the condition either. Neither the owner nor holders can trigger a replacement request. This is a distinct defect from the queue amplification: even an order-independent design needs an expiry/re-request path for the affected token itself.","line":38,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: phase 1 complete, VRF config set, subscription balance 0 (ChainlinkMockIntegration.test_UnfundedSubscriptionKeepsTokenPendingUntilFunded shows the request is accepted while unfunded). Alice burns 350,000 BMC via mintMystery -> token 41, request R41, status AwaitingRandomness. No funding for 25 hours; Chainlink nodes drop R41. Owner funds the subscription afterwards. Expected: token 41 is eventually revealed (or a new request can be opened for it). Actual: no function can open another request for token 41; it stays AwaitingRandomness forever, and finalizeReveals(n) returns 0 for the rest of the collection's life.","severity":"medium","snippet":"///  Chainlink VRF v2.5 requests remain pending while the subscription is unfunded and are fulfilled","title":"Contract and README assume unfunded VRF requests wait indefinitely; Chainlink expires them after 24h and the contract has no recovery path for an expired request"},{"citation":"resolved","description":"draw(seed) is a pure function of the seed alone: outcome[i] for every mint position i is fixed and publicly computable the moment the seed exists. The prototype has no notion of sale state, so any consumer that stores the seed before all 460 mystery positions are sold (or that lets minting continue after the request is sent, since the VRF node knows the word before it submits the fulfilment transaction) lets a minter pick exactly the positions that map to Genesis and skip the rest, destroying the 37/460 ex-ante fairness. The experimental test consumer (ExperimentalVRFConsumer.requestOnce) can be called at any time and does not freeze minting, so the tests do not catch this. The function also costs 503,870 gas per call, so a production consumer must compute the permutation once (or lazily in views), not per token in the VRF callback.","line":17,"path":"src/experimental/BMCPassVRFPrototype.sol","reproduction":"Seed 987654321 (the value used in testVRFSeedProducesExactUniqueInventory). prototype.draw(987654321) gives outcomes < 37 (Genesis, tierOfOutcome == 2) at positions 10, 21, 38, 47 and 61 (outcomes 15, 9, 24, 19, 14) and Standard at every other position below 61. If positions map to token ids 41+i and the seed is public while position 10 is unsold, a minter who waits until totalMinted == 50 and then mints receives a guaranteed Genesis; every other minter who reads the same seed avoids the Standard positions. Expected: every mystery mint has the same 37/460 chance regardless of timing. Actual: outcome per position is known in advance.","severity":"medium","snippet":"    function draw(uint256 seed)","title":"Prototype single-seed permutation exposes every unsold position's outcome once the seed is known, enabling position targeting unless the seed is requested only after the sale is closed"},{"citation":"resolved","description":"Architecture B concentrates all randomness into one request. The only consumer in the repository (ExperimentalVRFConsumer) hard-blocks a second request and has no timeout, so the 24h Chainlink expiry or a failed callback (success=false, request deleted) leaves seed == 0 forever and no outcome can ever be derived. testUnfulfilledRequestDoesNotCreateSeed records this freeze as expected behaviour. A production version of B must have a permissionless re-request after a timeout (well past Chainlink's 24h window) with first-delivered-word-wins semantics so that neither the owner nor a front-runner of the fulfilment transaction can discard a delivered word (a design where a re-request invalidates the previous request would let anyone who sees the node's fulfilment transaction in the mempool reroll it).","line":23,"path":"test/BMCPassVRFChainlinkIntegration.t.sol","reproduction":"consumer.requestOnce() returns request id R. R is never fulfilled (subscription unfunded for >24h, or the callback fails). vm.warp(+200 days). Expected: a replacement request can be opened and the collection still reveals. Actual: consumer.requestOnce() reverts with 'already requested' (testCannotRequestSecondSeed), consumer.seed() stays 0, received() stays false, and prototype.draw has no seed to run on; 460 tokens would remain unrevealed permanently.","severity":"medium","snippet":"        require(requestId == 0, \"already requested\");","title":"Prototype's reference consumer allows exactly one VRF request with no re-request, so a single expired or failed request freezes the whole 460-token distribution"},{"citation":"resolved","description":"The launch recipe forbids anything configured after deployment rather than in a constructor. A freshly deployed BMCIlluminatiPass cannot mint at all until the owner sends setGenesisImagesCID and setVRFConfig, and setVRFConfig stays callable forever (subscription id, key hash, callback gas, confirmations, payment mode). The subscription can be created before deployment (its id does not depend on the consumer address), so subscriptionId, keyHash, callbackGasLimit, requestConfirmations and nativePayment can all be constructor arguments. The CID is a dynamic string, which the launch constructor does not support, but a CIDv1 can be passed as its 32-byte sha2-256 digest (bytes32) and rendered on-chain as the base16 CID string 'f01701220' + hex(digest) (dag-pb) or 'f01551220' + hex(digest) (raw), which gateways resolve. Keeping setVRFConfig as a bounded owner power is a documented trust assumption, not a bypass; the finding is the post-deployment initialisation.","line":186,"path":"src/BMCIlluminatiPass.sol","reproduction":"Deploy BMCIlluminatiPass(owner, bmc, coordinator) through the factory. Call mintGenesis() from a funded, approved account. Expected under the launch policy: the contract is fully configured and usable at deployment. Actual: revert GenesisCIDNotSet(); after setGenesisImagesCID, mintMystery still reverts VRFNotConfigured() until a second owner transaction; test_DeployScriptConfig asserts exactly this two-step state.","severity":"medium","snippet":"    function setGenesisImagesCID(string calldata cid) external onlyOwner {","title":"Launch-critical configuration (Genesis CID and VRF parameters) is set after deployment by owner transactions instead of in the constructor"},{"citation":"resolved","description":"The comment claims ~85k worst case per reveal. Measured on this code with the project's MockVRFCoordinator: finalizeReveals(1) costs 133,705 gas for the first reveal and 116,601 for the next one (cold storage of revealedCount, totalMinted, _slotSwap, mysteryGenesisRevealed, _tier, _artworkId). With the minimum allowed callbackGasLimit of 150,000 the computed batch is always 0, so every single reveal needs a separate permissionless finalizeReveals transaction paid by someone else. With larger limits an under-budgeted batch reverts inside try/catch and is retried by keepers; the stored word is never lost, so this is cost and UX, not safety. Raise GAS_PER_REVEAL to about 140k or re-measure under the real coordinator's _callWithExactGas.","line":95,"path":"src/BMCIlluminatiPass.sol","reproduction":"Config setVRFConfig(42, keyHash, 150_000, 3, false). Mint token 41, fulfill its request with the request's own callback gas limit (MockVRFCoordinator.fulfill). Expected per the natspec: the callback finalizes the reveal inline. Actual: revealStatusOf(41) == RandomnessStored (2); a separate finalizeReveals(1) call is required and costs 133,705 gas.","severity":"low","snippet":"    uint256 public constant GAS_PER_REVEAL = 110_000;","title":"GAS_PER_REVEAL (110k) is below the measured cost of one reveal, so the inline callback batch reverts or is skipped more often than designed"},{"citation":"resolved","description":"Drawing without replacement in mint order means the probability of Genesis for the next position is remainingMysteryGenesis()/remaining, which the contract publishes (the website shows 'remaining-inventory odds'). Every minter pays the same 350,000 BMC but their ex-ante chance depends on when they mint. This is symmetric public information, not a bypass, and the README discloses it, but it is a rarity bias relative to the stated 37/460 fairness and it is the reason informed buyers will wait while uninformed ones mint early. The single-seed design (B) gives every position the same 37/460 chance.","line":334,"path":"src/BMCIlluminatiPass.sol","reproduction":"State: revealedCount == 300 and mysteryGenesisRevealed == 10 (27 Genesis among 160 remaining). Next mystery mint: Genesis chance 27/160 = 16.9%. Alternative state: revealedCount == 300 and mysteryGenesisRevealed == 30: chance 7/160 = 4.4%. Expected under a flat-odds launch: 37/460 = 8.04% for everyone. Actual: the chance varies between those values and is readable before minting via remainingMysteryGenesis() and pendingReveals().","severity":"low","snippet":"        uint256 r = uint256(keccak256(abi.encode(word, tokenId))) % remaining;","title":"Odds of a Genesis outcome drift with reveal state and are public, so mint timing changes expected rarity (design A only)"},{"citation":"resolved","description":"VRFRecovery, VRFRecoveryAdditional and VRFSafetyProperties all pass on the current code because they only document the current behaviour; none of them fails if a token can never be revealed. Missing coverage: (a) a fulfilment where the callback fails (the Chainlink mock emits success=false and deletes the request) and the resulting permanent AwaitingRandomness state; (b) a request older than Chainlink's 24h window and any recovery path; (c) a fulfilment delivered at exactly the minimum callbackGasLimit of 150,000 through VRFCoordinatorV2_5Mock (only the project's own mock at 95k is tested); (d) ordering fuzz over more than three concurrently pending tokens showing outcomes are independent of delivery order; (e) BMCMainnetFork.t.sol, which needs an RPC and was not run here, so burnFrom against the real BMC token remains unverified; (f) nothing checks the Chainlink mock's unfunded-at-request path together with later funding beyond one token.","line":123,"path":"test/VRFRecovery.t.sol","reproduction":"Run forge test: 55 tests pass, including testMissingEarlierRequestBlocksLaterReveal which asserts finalizeReveals(10) == 0 after 200 days with token 42's randomness stored. Expected for a recovery suite: a test that fails while a holder with delivered randomness cannot be revealed. Actual: no test fails; the defect in the high finding is invisible to CI.","severity":"low","snippet":"        assertEq(pass.finalizeReveals(10), 0);","title":"VRF recovery and safety test suites assert the freeze as expected behaviour and never exercise a failed callback, an expired request, or recovery"},{"citation":"resolved","description":"The README tells holders that an unfunded request waits until funded and that the coordinator being decommissioned is the only way a token stays pending. Chainlink's documentation states that requests on an under-funded subscription expire after 24 hours. The privileged-powers table should list that the subscription owner can, by not funding the subscription for 24h, permanently freeze the affected token and (under the current design) every later one, and that no owner action can undo it.","line":64,"path":"README.md","reproduction":"Read README.md lines 63-67 against 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'). Expected: documentation matches the dependency's behaviour. Actual: it promises indefinite pending.","severity":"info","snippet":"re-request or fallback. Chainlink VRF v2.5 keeps a request pending while the subscription is","title":"README misstates Chainlink VRF v2.5 pending-request behaviour and omits the owner's ability to freeze reveals by not funding"},{"citation":"resolved","description":"Read in full: src/BMCIlluminatiPass.sol, src/StandardArt.sol, src/interfaces/IBMCToken.sol, src/experimental/BMCPassVRFPrototype.sol, script/Deploy.s.sol, all eight test files and both mocks, the vendored VRFCoordinatorV2_5Mock and SubscriptionAPI, README and docs. Not reached: the live BMC token and the live VRF coordinator (no fork RPC; BMCMainnetFork.t.sol not run), and the full VRFCoordinatorV2_5 source (not vendored; behaviour taken from Chainlink docs and the vendored mock). All 55 existing tests pass; runtime size 23,488 bytes; no DELEGATECALL/CALLCODE/SELFDESTRUCT, no proxy, no receive/fallback. Entry points: setGenesisImagesCID (owner, once), setVRFConfig (owner, unbounded in time), mintGenesis/mintMystery (anyone paying), rawFulfillRandomWords (immutable coordinator only), finalizeReveals (anyone), standard ERC721/Ownable. No reroll, owner-assignment, selective-fulfilment or reentrancy path was found in A: the word is bound to the token before any reveal, re-delivery is ignored, in-order processing removes finalization-order choice, and _burnExact fails closed. ARCHITECTURE A verdict: cannot safely handle an expired or permanently unavailable request. One lost request freezes that token and all later ones permanently with no recovery (high finding); the owner/subscription owner can cause it by inaction; odds drift with timing. ARCHITECTURE B verdict: the prototype is not an architecture yet (pure function, no request, no mint, no state). As a design it is fairer (flat 37/460 odds, one request to fund and monitor, no ordering question) but it is only safe if (1) the seed is requested after minting is closed and never before, (2) a permissionless timed re-request exists with first-delivered-word-wins, and (3) the sale close is itself rule-based (sell-out or a deadline), so neither of the two existing designs satisfies the requirements as written. RECOMMENDATION: keep BMCIlluminatiPass's mint, burn, pricing, 40 sequential Genesis, storage and metadata, and replace the per-mint request with the single-seed model: mintMystery assigns position only; minting closes at 500 or at a fixed deadline (permissionless close); after close anyone may call requestSeed(); if no word has arrived after a timeout of at least 7 days anyone may call requestSeed() again; rawFulfillRandomWords stores the first word for any request id the contract issued and ignores later ones; outcomes come from the existing O(1) virtual Fisher-Yates driven by keccak256(seed, tokenId) in mint order via the existing permissionless finalizeReveals, so no 500k-gas permutation is needed and all existing invariant tests still apply. If per-mint requests must stay, the minimal fix is: a permissionless re-request for a token whose request is older than a timeout, first-delivered-wins (never invalidate an open request, to block mempool rerolls), plus the same strict order. RESIDUAL RISKS of the recommended design: Chainlink node operators know the word before submitting and, with two open requests, could choose which to deliver (mitigated by the long timeout); the reveal of all 460 happens only after close, so early minters wait longer; the subscription must stay funded for one request; the owner's setVRFConfig power remains a trust assumption. MAINNET READINESS: not ready. Blocking: the high finding, an expiry/re-request path, constructor-time configuration for the launch, a corrected README, and tests that fail on the freeze. Also pending: the real-BMC burnFrom fork check (O-3) and an external gas measurement under the real coordinator.","line":40,"path":"src/BMCIlluminatiPass.sol","reproduction":"Summary entry; the concrete inputs are in the individual findings above. The decisive state is: token 41 AwaitingRandomness with its request expired, token 42 RandomnessStored, finalizeReveals(10) == 0 permanently.","severity":"info","snippet":"contract BMCIlluminatiPass is ERC721, Ownable, ReentrancyGuard {","title":"Architecture comparison, recommendation and mainnet-readiness assessment (A: 460 in-order requests vs B: single-seed permutation)"}],"hash":"87618a78ea917c0dcc0f56b9b5ef8e947d0653f4038ecb0d99d6df7ec2f51b7a","nodeId":"36a63b1a-e49b-424a-be54-cfe392e3e870","outcome":"completed","summary":"Review complete. The findings file is at the repository root with 10 entries, one carrying a Foundry proof that fails on the current code.\n\n**Verdict: neither architecture can safely handle an expired or permanently unavailable VRF request.** Architecture A has no reroll, owner-assignment, selective-fulfilment or reentrancy path, but it cannot survive a single lost request. Architecture B is not yet an architecture, and its only reference consumer freezes completely on one lost request.\n\n**What I read.** All of `src/`, `script/Deploy.s.sol`, all eight test files and both mocks, the vendored Chainlink `VRFCoordinatorV2_5Mock` and `SubscriptionAPI`, README and docs. Not reached: the live BMC token and live coordinator (no fork RPC, so `BMCMainnetFork.t.sol` was not run) and the full `VRFCoordinatorV2_5` source, which is not vendored. All 55 existing tests pass. Runtime size is 23,488 bytes with no forbidden opcodes.\n\n**Findings, most severe first.**\n\n- **High, head-of-line blocking.** One request that is never fulfilled freezes that token and every later token forever, even those whose random word is already stored. Chainlink's docs state unfunded requests expire after 24 hours, and the coordinator accepts requests regardless of balance, so a subscription running dry overnight mid-sale triggers it. A failed callback also discards the word. No owner action can repair it. The proof test mints 41, 42, 43, delivers only 42 and 43, and asserts they reveal. It fails now with `2 != 3`.\n- **Medium, no expiry recovery.** The contract and README claim unfunded requests wait indefinitely. They do not. The subscription owner can freeze the collection by simply not funding for 24 hours, an undisclosed power.\n- **Medium, prototype position targeting.** `draw(seed)` fixes every position's outcome. For seed 987654321, positions 10, 21, 38, 47 and 61 are Genesis. Any consumer that stores the seed before the sale closes lets minters cherry-pick. The function also costs about 504k gas.\n- **Medium, prototype single request.** The reference consumer blocks a second request and has no timeout. One lost request leaves 460 tokens unrevealed.\n- **Medium, post-deployment configuration.** CID and VRF parameters are set by owner transactions after deployment, which the launch recipe forbids. Subscription id and VRF parameters can move to the constructor. The CID can be passed as a bytes32 digest and rendered as a base16 CIDv1.\n- **Low.** The 110k per-reveal gas budget is below the measured 116k to 134k, so the inline batch is skipped or reverts more than designed, with no loss. Genesis odds drift with reveal state and are public, so timing changes rarity. The recovery test suites assert the freeze as expected behaviour and never exercise a failed callback or recovery.\n\n**Recommended design.** Keep the mint, burn, pricing, 40 sequential Genesis, storage and metadata of `BMCIlluminatiPass`. Replace per-mint requests with one seed requested only after minting closes, at 500 or at a fixed deadline. Allow a permissionless re-request after a timeout of at least 7 days, with first-delivered-word-wins so no one can discard a delivered word. Drive the existing O(1) virtual Fisher-Yates from the seed and token id in mint order through the existing permissionless `finalizeReveals`. Residual risks: node operators see words before submitting, early minters wait until sale close, and the subscription must stay funded for one request.\n\n**Mainnet readiness: not ready.** Blocking items are the high finding, an expiry and re-request path, constructor-time configuration, a corrected README, tests that fail on the freeze, and the real-BMC `burnFrom` fork check.","treeHash":null,"usage":{"cachedInputTokens":1468974,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":43402,"runtime":"claude","turns":43,"wallClockMs":649543}}],"verification":[{"checks":[{"durationMs":7407,"exitCode":0,"name":"build","output":"Compiling 69 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.22s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n  --> test/BMCPassVRFPrototype.t.sol:46:5:\n   |\n46 |     function testSameSeedProducesSameDistribution() public {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to view\n  --> test/BMCPassVRFPrototype.t.sol:53:5:\n   |\n53 |     function testDifferentSeedsProduceDifferentDistributions() public {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":699,"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 439.58µs (105.25µs CPU time)\n\nRan 2 tests for test/VRFSafetyProperties.t.sol:VRFSafetyPropertiesTest\n[PASS] testDelayedPredecessorDoesNotBurnAgainOrChangeSupply() (gas: 1393730)\n[PASS] testOnlyCoordinatorMayDeliverRandomness() (gas: 472409)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.48ms (1.21ms CPU time)\n\nRan 1 test for test/VRFRecoveryAdditional.t.sol:VRFRecoveryAdditionalTest\n[PASS] testLaterRandomnessStoredUntilEarlierRequestArrives() (gas: 1383548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 20.07ms (723.18µs CPU time)\n\nRan 4 tests for test/ChainlinkMockIntegration.t.sol:ChainlinkMockIntegrationTest\n[PASS] test_NotAConsumerReverts() (gas: 367150)\n[PASS] test_RequestAndFulfilThroughChainlinkMock() (gas: 779150)\n[PASS] test_SeveralRequestsOutOfOrderThroughChainlinkMock() (gas: 1950517)\n[PASS] test_UnfundedSubscriptionKeepsTokenPendingUntilFunded() (gas: 969009)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 138.77ms (2.21ms CPU time)\n\nRan 12 tests for test/VRFRecovery.t.sol:VRFRecoveryTest\n[PASS] testCallbackFailureLosesWordAndRerequestRecovers() (gas: 11793608)\n[PASS] testFirstDeliveredWordWinsOriginalFirst() (gas: 1192077)\n[PASS] testFirstDeliveredWordWinsReplacementFirst() (gas: 974415)\n[PASS] testFuzz_DeliveryOrderDoesNotChangeOutcomes(uint256,uint256) (runs: 64, μ: 4951162, ~: 4951164)\n[PASS] testLostEarlierRequestRecoversWithoutFreezingLaterTokens() (gas: 2322552)\n[PASS] testMinimumCallbackGasStoresWordThroughChainlinkMock() (gas: 11134333)\n[PASS] testOwnerHasNoSpecialRerequestPower() (gas: 563802)\n[PASS] testRerequestNotAllowedBeforeDelay() (gas: 725116)\n[PASS] testRerequestOnlyForTokensAwaitingRandomness() (gas: 761768)\n[PASS] testRerequestRateLimitedPerToken() (gas: 871324)\n[PASS] testRerequestUsesCurrentConfigAndRollsBackIfRequestFails() (gas: 989615)\n[PASS] testSeveralUnfundedTokensRecoverAfterFunding() (gas: 4524654)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 138.82ms (159.09ms CPU time)\n\nRan 4 tests for test/BMCPassVRFPrototype.t.sol:BMCPassVRFPrototypeTest\n[PASS] testDifferentSeedsProduceDifferentDistributions() (gas: 1109240)\n[PASS] testDistributionHasNoDuplicates() (gas: 4831168)\n[PASS] testInvalidOutcomeReverts() (gas: 8495)\n[PASS] testSameSeedProducesSameDistribution() (gas: 1109275)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 504.30ms (17.71ms CPU time)\n\nRan 7 tests for test/BMCPassVRFChainlinkIntegration.t.sol:BMCPassVRFChainlinkIntegrationTest\n[PASS] testCannotRequestSecondSeedBeforeTimeout() (gas: 325849)\n[PASS] testFirstDeliveredSeedWinsWhenOriginalArrivesLate() (gas: 659818)\n[PASS] testLostSeedRequestIsReplacedAfterTimeout() (gas: 725047)\n[PASS] testOnlyCoordinatorMaySupplySeed() (gas: 292102)\n[PASS] testPoC_PublicSeedExposesEveryPositionOutcome() (gas: 3182283)\n[PASS] testSeedCannotBeRequestedWhileMintingIsOpen() (gas: 31566)\n[PASS] testVRFSeedProducesExactUniqueInventory() (gas: 5903852)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 504.37ms (25.25ms CPU time)\n\nRan 43 tests for test/BMCIlluminatiPass.t.sol:BMCIlluminatiPassTest\n[PASS] testFuzz_SingleRevealAlwaysInInventory(uint256) (runs: 64, μ: 10984038, ~: 10980333)\n[PASS] test_CIDIsConstantAndUsedInGenesisMetadata() (gas: 1426565)\n[PASS] test_CallbackBatchIsGasAware() (gas: 12129868)\n[PASS] test_CallbackBatchWith500kRevealsThree() (gas: 12590278)\n[PASS] test_Constants() (gas: 82490)\n[PASS] test_ConstructorRejectsInvalidVRFConfig() (gas: 10746)\n[PASS] test_DeployScriptConfig() (gas: 10917420)\n[PASS] test_DeployScriptRequiresOwner() (gas: 5997455)\n[PASS] test_ExcessAllowanceOnlyBurnsPrice() (gas: 341754)\n[PASS] test_FinalizationOrderCannotBeChosen() (gas: 11976960)\n[PASS] test_FirstMinterGetsLegendaryGenesis1() (gas: 2070145)\n[PASS] test_FreshDeploymentMintsWithoutAnyOwnerCall() (gas: 307796)\n[PASS] test_FulfillRevealsAndLocksMetadata() (gas: 17272651)\n[PASS] test_Full500MintSimulation() (gas: 278134243)\n[PASS] test_FullMintOutWithInOrderFulfillment() (gas: 244467414)\n[PASS] test_GasPerRevealBudgetCoversMeasuredCost() (gas: 11583902)\nLogs:\n  first reveal gas: 129539\n  second reveal gas: 112439\n\n[PASS] test_GenesisRevertsAfterPhase1() (gas: 10532760)\n[PASS] test_Mints2To40AreSequentialGenesis() (gas: 14428859)\n[PASS] test_MysteryBurnRevertsIfVRFRequestFails() (gas: 10666741)\n[PASS] test_MysteryRevertsDuringPhase1() (gas: 455814)\n[PASS] test_MysteryUsesConstructorConfigWithoutOwnerCall() (gas: 10792755)\n[PASS] test_NoOwnerMintOrWithdrawSurface() (gas: 58291)\n[PASS] test_OnlyCoordinatorCanFulfill() (gas: 10898228)\n[PASS] test_OutOfOrderFulfillmentFinalizesInMintOrder() (gas: 12249861)\n[PASS] test_PendingMetadataIsNeutral() (gas: 13582571)\n[PASS] test_PermissionlessFinalizationWhenCallbackGasTooLow() (gas: 11065772)\n[PASS] test_Phase1CostsExactlyOneMillion() (gas: 390236)\n[PASS] test_Phase2CostsExactly350k() (gas: 10953694)\n[PASS] test_ReentrancyGenesisBlocked() (gas: 884668)\n[PASS] test_ReentrancyMysteryBlocked() (gas: 11367627)\n[PASS] test_RevertInsufficientAllowance() (gas: 179241)\n[PASS] test_RevertInsufficientBalance() (gas: 193091)\n[PASS] test_RevertNoAllowance() (gas: 144187)\n[PASS] test_RevertWhenTokenDecimalsUnexpected() (gas: 728852)\n[PASS] test_RevertWhenTokenDoesNotActuallyBurn() (gas: 745318)\n[PASS] test_StandardArtCombinationsUnique() (gas: 1482850)\n[PASS] test_StandardArtCoversEveryTrait() (gas: 743677)\n[PASS] test_StandardArtRendersWithoutGenesisTraits() (gas: 30070029)\n[PASS] test_StandardMetadataAfterReveal() (gas: 19961234)\n[PASS] test_TransfersAreStandard() (gas: 469634)\n[PASS] test_UnavailableRandomnessStaysPendingNoFallback() (gas: 10993135)\n[PASS] test_VRFConfigValidation() (gas: 253119)\n[PASS] test_ViewsRevertForNonexistent() (gas: 33596)\nSuite result: ok. 43 passed; 0 failed; 0 skipped; finished in 504.47ms (1.87s CPU time)\n\nRan 8 test suites in 507.69ms (1.82s CPU time): 73 tests passed, 0 failed, 1 skipped (74 total tests)\n","passed":true},{"durationMs":1674,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"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)\"],\"files\":{\".github/workflows/main.yml\":43,\".gitignore\":5,\"ADAPTATION.md\":64,\"README.md\":161,\"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/SECURITY-REVIEW.md\":47,\"docs/TEST-RESULTS.md\":98,\"docs/VRF-ARCHITECTURE-REVIEW.md\":157,\"foundry.toml\":25,\"remappings.txt\":3,\"script/Deploy.s.sol\":78,\"src/BMCIlluminatiPass.sol\":581,\"src/StandardArt.sol\":236,\"src/experimental/BMCPassVRFPrototype.sol\":51,\"src/interfaces/IBMCToken.sol\":18,\"test/BMCIlluminatiPass.t.sol\":927,\"test/BMCMainnetFork.t.sol\":52,\"test/BMCPassVRFChainlinkIntegration.t.sol\":189,\"test/BMCPassVRFPrototype.t.sol\":64,\"test/ChainlinkMockIntegration.t.sol\":117,\"test/VRFRecovery.t.sol\":411,\"test/VRFRecoveryAdditional.t.sol\":71,\"test/VRFSafetyProperties.t.sol\":81,\"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":3421,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/BMCIlluminatiPass.sol:322: Reentrancy in BMCIlluminatiPass._burnExact(address,uint256) (src/BMCIlluminatiPass.sol#322-333):\n[medium/medium] reentrancy-no-eth at src/BMCIlluminatiPass.sol:272: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#272-285):\n[medium/medium] reentrancy-no-eth at src/BMCIlluminatiPass.sol:256: Reentrancy in BMCIlluminatiPass.mintGenesis() (src/BMCIlluminatiPass.sol#256-269):\n[medium/medium] uninitialized-local at src/experimental/BMCPassVRFPrototype.sol:25: BMCPassVRFPrototype.draw(uint256).pool (src/experimental/BMCPassVRFPrototype.sol#25) is a local variable never initialized\n[medium/medium] unused-return at src/BMCIlluminatiPass.sol:342: BMCIlluminatiPass.rawFulfillRandomWords(uint256,uint256[]) (src/BMCIlluminatiPass.sol#342-364) ignores return value by this.finalizeReveals(batch) (src/BMCIlluminatiPass.sol#363)\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:256: Reentrancy in BMCIlluminatiPass.mintGenesis() (src/BMCIlluminatiPass.sol#256-269):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:322: Reentrancy in BMCIlluminatiPass._burnExact(address,uint256) (src/BMCIlluminatiPass.sol#322-333):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:272: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#272-285):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:303: Reentrancy in BMCIlluminatiPass._requestRandomWord(uint256) (src/BMCIlluminatiPass.sol#303-319):\n[low/medium] reentrancy-benign at src/BMCIlluminatiPass.sol:272: Reentrancy in BMCIlluminatiPass.mintMystery() (src/BMCIlluminatiPass.sol#272-285):\n[low/medium] timestamp at src/BMCIlluminatiPass.sol:292: BMCIlluminatiPass.rerequestRandomness(uint256) (src/BMCIlluminatiPass.sol#292-300) uses timestamp for comparisons","passed":true},{"durationMs":876,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/BMCIlluminatiPass.sol:47: Centralization Risk (2 places)\n[low] costly-loop at src/BMCIlluminatiPass.sol:372: 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:90: Large Numeric Literal (7 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":"00ff9bf19e261f4acf2fe665cd7cf6b36686edda899e4c613fdfab4aefd0844e","verifiedTreeHash":"ae0daee3b29652de2811161c40278399c764f7df","verifierVersion":"0.1.0+fb8278c5"}]}