{"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":"7377af7e-bc2d-441d-bbaf-5d18937a30c7","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"051f0b1edde1d3de004ef314d66aaaa3ee30a35018fe71b91275bea3676ba564","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"abbffad3be2f4114547a658791bc7b56cb64eddb5970090625e7da7c4fb30d32","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"a8480c9f7cab088480ee28f6a1ecf922e34578ad6cca3a88ff744190cdfa99b9","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"0caf1271114a31956626d6925eea5056974b85b6e81aed8b7d15d35da81c2ccc","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f451089d8691cd18bbf16544881868871e74ccc7127e4bdd39fc9e39bb3893be","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"5de0d2080e0fdb68c9f56795be111c4fcfa91654a487df2e70a4bd139ac3fb5d","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"a368356ceb1ac2d76af163b26844bdacd666cf5e5577f57cb33833c9d5c04f90","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"25d7c8af1f7f2469209c40feb798a0e50714e3d74af530f20452a9feded4855e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"OGPass: a soulbound ERC-721 membership NFT on Ethereum mainnet. NOT an ERC-20; no fungible token, no pool. NFT name \"OG Pass\", symbol \"OGPASS\", no fixed total supply (minted by claim or bonding curve).\nPayment token: existing OG ERC-20 at 0xce7eb1ad9e2e1c784ea05f7ea4a0fe625923d10a (18 decimals, no transfer fee). Burn address 0x000000000000000000000000000000000000dEaD.\nConstructor: (og, founderRoot, founderCount, owner) with founderRoot = 0xb9c8ed1655d96a99d756f50819afd0c008b1097497edba9ee4fdb1b7ce2dc6c8, founderCount = 49, owner = $owner. Claim deadline = deploy time + 30 days, immutable.\nSoulbound: every transfer, approve and setApprovalForAll reverts. Max one pass per wallet (founder or public).\nFounder passes: claimFounder(bytes32[] proof) with OpenZeppelin standard Merkle leaves keccak256(bytes.concat(keccak256(abi.encode(address)))); one per address, before the deadline; off-curve; cannot be sold into the curve. Optional fixed buyback: sellFounder(tokenId) burns the pass and pays exactly 100,000 OG from a separate founder reserve. fundFounderReserve(amount) lets anyone deposit OG into it. After the deadline, anyone can call burnUnclaimedFounderReserve() which sends the reserve not needed for still-held founder buybacks to dEaD.\nPublic curve: price of the n-th outstanding public pass P(n) = 1,000,000e18 + 10,000e18 * (n - 1). buy(maxPrice, deadline) mints one pass for exactly P(publicSupply+1) pulled via transferFrom (revert if > maxPrice or past deadline). sell(tokenId, minOut, deadline) burns a public pass and pays 92.5% of P(publicSupply) to the seller, sending 7.5% to dEaD (revert if < minOut or past deadline). Curve reserve must always equal the sum of P(i) for outstanding public passes; selling every pass in any order empties it to zero.\nOwner can only: setBaseURI, and pause/unpause NEW buys. Owner can never pause sells, claims or founder buybacks, withdraw any OG, change prices, the root or the deadline. No upgradeability, no proxy.\nViews: currentBuyPrice, currentSellPrice, publicSupply, founderClaimed, curveReserve, founderReserve, isMember(address), isFounder(tokenId).\nSecurity: ReentrancyGuard, checks-effects-interactions, verify received amount on every OG deposit, custom errors, events for Claim, Buy, Sell, FounderSell, ReserveFunded, ReserveBurned, BuyPause.\nTests (Foundry): soulbound reverts, Merkle claim valid/invalid/double/after deadline, buy/sell slippage and deadline, fuzz random buy/sell sequences with invariant curveReserve == sum of outstanding prices and solvency, founder buyback accounting, burnUnclaimed math, one-pass-per-wallet, pause only blocks buys.\nTotal supply cap: at most 1,000 passes outstanding in total (49 founder + up to 951 public); buy() reverts once the cap is reached. Burned passes free up room.\n\nTotal supply: 1000","parentJobId":null,"planHash":"c5d6931ce3f21bbc9e288d9cbfaa3720149734a8ada3bd7b0394c4b9b63690b1","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"7377af7e-bc2d-441d-bbaf-5d18937a30c7","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1343-ogpass-soulbound-erc-721-membership"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52397","feedbackHash":"c4e6b2c070eac21b5437bf6fe79eb54cd9731c358037d1a607852e5fee700402","nodeKey":"audit_economics","submissionHash":"051f0b1edde1d3de004ef314d66aaaa3ee30a35018fe71b91275bea3676ba564","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51144","feedbackHash":"9eb04ce126f4367eaff195e9fd03be5d7e625d0f72969b4f74d6c1f3382b6c9f","nodeKey":"audit_flow","submissionHash":"abbffad3be2f4114547a658791bc7b56cb64eddb5970090625e7da7c4fb30d32","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51375","feedbackHash":"c6d1cece6e3946d5a83de712a7b54a71754a5fb64d4d1cf144c9db6971287a69","nodeKey":"audit_judge","submissionHash":"a8480c9f7cab088480ee28f6a1ecf922e34578ad6cca3a88ff744190cdfa99b9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51494","feedbackHash":"527563c3119d3d454f4955e4a83cca593607c81c374ff2b90940104616740fef","nodeKey":"audit_math","submissionHash":"0caf1271114a31956626d6925eea5056974b85b6e81aed8b7d15d35da81c2ccc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50903","feedbackHash":"519a35c55ad9b595847d2cbc60649b9fb03edf1acd53eb0ed7be17d170585aa8","nodeKey":"audit_permissions","submissionHash":"f451089d8691cd18bbf16544881868871e74ccc7127e4bdd39fc9e39bb3893be","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51507","feedbackHash":"65f9ce5982d48e6ef3fe797221f5e74331ae0c5fcb78454bd1e9abf0a4615885","nodeKey":"build_contract_project","submissionHash":"5de0d2080e0fdb68c9f56795be111c4fcfa91654a487df2e70a4bd139ac3fb5d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52190","feedbackHash":"cf9c16c68fd7ad7f00048b792527014b3b4af518f97b5fdc11a5a8667cc9240b","nodeKey":"manifest","submissionHash":"a368356ceb1ac2d76af163b26844bdacd666cf5e5577f57cb33833c9d5c04f90","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51481","feedbackHash":"ce3a759849db37b79a59b658782dd186498635d6c3c4585eb1eb2a8680981a26","nodeKey":"write_foundry_tests","submissionHash":"25d7c8af1f7f2469209c40feb798a0e50714e3d74af530f20452a9feded4855e","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"7fb6de2a84cf40db0cb8df47b6eba450fd52cbaa9af9b1566e71ee962a3c61d8","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"827152cce766ca0d","findings":[{"citation":"resolved","description":"OGPass inherits Ownable2Step without overriding renounceOwnership(). The brief gives the owner exactly two powers: setBaseURI and a pause/unpause of NEW buys. Renouncing while paused turns that reversible pause into a permanent shutdown of the public curve's mint side. No new public pass can then ever be minted, and existing holders can only exit, which pays 92.5% of a falling price. Sells, claims and buybacks still work, so no funds are locked, but a guarantee of the design is broken by a single owner call, which may be a mistake. Suggested fix: override renounceOwnership() to revert, or to require !buysPaused (or to unpause first).","line":288,"path":"src/OGPass.sol","reproduction":"Deploy OGPass(og, root, 49, owner). As owner: setBuysPaused(true), then renounceOwnership(). owner() == address(0). Any user with enough allowance calls buy(type(uint256).max, block.timestamp): it reverts BuysPaused, and since setBuysPaused is onlyOwner, nothing can ever clear it. Expected: an owner limited to a reversible pause cannot disable buys forever. Actual: buys are disabled for good. I ran this as a Foundry test against the current tree and it behaved as described (the test asserts the revert).","severity":"low","snippet":"    function setBuysPaused(bool paused) external onlyOwner {\n        buysPaused = paused;","title":"Owner can make the buy pause permanent: renounceOwnership() while buysPaused == true leaves nobody able to unpause"}],"hash":"051f0b1edde1d3de004ef314d66aaaa3ee30a35018fe71b91275bea3676ba564","nodeId":"d7bc6315-1780-47a2-b30d-18d46a7b70ca","outcome":"completed","summary":"I found one low-severity defect and no economic defects in `src/OGPass.sol`. The curve, founder reserve, supply cap and soulbound rules all match the brief. The results are in `.imd-findings.json`, which is the only file I wrote; I changed nothing in the source.\n\n**Finding 1 (low): the owner can make the buy pause permanent** (`src/OGPass.sol:288`)\n- **What happens:** the contract keeps the standard `renounceOwnership()` from OpenZeppelin. If the owner calls `setBuysPaused(true)` and then `renounceOwnership()`, `owner()` becomes the zero address. After that every `buy()` reverts with `BuysPaused`, and nobody can ever unpause.\n- **Why it matters:** the brief gives the owner a pause they can undo, not a way to shut down new buys for good. No funds are locked, because sells, claims and founder buybacks still work.\n- **Evidence:** I ran this sequence as a Foundry test against the current code and it behaved as described. The test file was in `test/scratch/`, which I deleted afterwards.\n- **Suggested fix:** override `renounceOwnership()` so it reverts, or so it only works while buys are not paused.\n\n**Coverage:** the file has a row for each of the 14 entry points: 13 `holds` and 1 `finding` (`renounceOwnership`), plus a row for the solvency invariant. In summary:\n- **Curve reserve:** it always equals the sum of P(i) for outstanding public passes. Prices are multiples of 1e22, so the 92.5% / 7.5% split leaves no rounding dust, and selling every pass in any order empties the reserve to zero.\n- **Front-running:** buying just before someone else and selling just after loses money at every supply level (-75k + 9,250 - 750·s OG).\n- **Founder reserve:** `burnUnclaimedFounderReserve()` keeps exactly 100,000 OG for each founder pass still held. `sellFounder` pays only from the founder reserve and never touches the curve reserve.\n- **Supply cap:** it counts unclaimed founder slots only until the deadline, and passes burned by selling free up room.\n- **Received-amount checks:** every OG deposit is verified to arrive in full.\n- **Static-analysis leads:** the Slither reentrancy lead in `_pullExact` can't be triggered with a plain ERC-20 like OG, because of the reentrancy guard and the received-amount check. The Aderyn \"ETH send\" lead doesn't apply: the contract never sends ETH.\n- **Not a finding:** OG sent straight to the contract can never be recovered. That follows from the rule that the owner can't withdraw any OG.\n\nThe existing test suite passes: 79 tests, including the fuzz and invariant runs.","treeHash":null,"usage":{"cachedInputTokens":247475,"inputTokens":12,"model":"claude-opus-5-5","outputTokens":6478,"runtime":"claude","turns":6,"wallClockMs":99521}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3a40eaafbd83a6bc","findings":[{"citation":"resolved","description":"Boundary x invariant seam (max input vs. the real payment token). The brief and the code promise 'up to 951 public passes' inside a 1,000 cap, priced on P(n) = 1,000,000 OG + 10,000 OG * (n - 1). The curve reserve must equal sum P(1..publicSupply), so holding 951 public passes requires sum P(1..951) = 951 * 1e24 + 1e22 * (951 * 950 / 2) = 5,468,250,000 OG locked in the contract. The live OG token at 0xce7eb1ad9e2e1c784ea05f7ea4a0fe625923d10a (read on mainnet on 2026-10-11: totalSupply() = 1,000,000,000e18, balanceOf(0xdEaD) = 52,689,865.7e18, totalBurned() matches) has a total supply of 1,000,000,000 OG. Even if every OG in existence were deposited into the curve, publicSupply could reach at most 358 (sum P(1..358) = 997,030,000 OG); with the already-burned dEaD balance excluded the ceiling is 347 (sum P(1..347) = 947,310,000 OG). Consequences: (a) SupplyCapReached on buy() can never trigger before the claim deadline, so the 'burned passes free up room' and 'cap' behaviour exists only in tests with a mintable mock; (b) the first pass alone costs 0.1% of all OG and the 347th costs 4,460,000 OG, so the documented membership size is not achievable; (c) the founder buyback of 100,000 OG is 10x cheaper than the cheapest public pass, which may or may not be intended. The contract matches the brief literally, so this is reported as a parameter-consistency defect for the requester to confirm rather than a code bug: if the intended prices were e.g. 1,000 OG + 10 OG per step (or OG were a different-decimals token), the constants need changing before deployment because they are immutable and the owner cannot change prices.","line":44,"path":"src/OGPass.sol","reproduction":"Arithmetic (no state needed): priceOf(1) = 1e24, priceOf(951) = 1e24 + 1e22 * 950 = 1.05e25; sum over n = 1..951 = 5.46825e27 wei = 5,468,250,000 OG > OG.totalSupply() = 1e27 wei. Scratch test test/scratch/MathProbe.t.sol::test_curveCapacityExceedsOgSupply computes sumPrices(951) = 5468250000000000000000000000 and finds the largest n with sumPrices(n) <= 1e27 is 358 (<= 1e27 - dEaD balance is 347). Expected per brief: public supply can reach 951 and buy() reverts with SupplyCapReached at the cap. Actual on mainnet: buy() will revert in OG.transferFrom (insufficient balance/allowance) long before, at publicSupply <= 347, and the cap branch at src/OGPass.sol:240 is dead code for the real token.","severity":"low","snippet":"    uint256 public constant BASE_PRICE = 1_000_000e18;","title":"Curve parameters exceed the OG token's total supply: the 951-public / 1,000-pass cap is unreachable and at most ~347 public passes can ever exist"},{"citation":"resolved","description":"Boundary (zero input) on a view. OpenZeppelin v5 ERC721.balanceOf reverts for the zero address (ERC721InvalidOwner(0)), so the brief's isMember(address) view reverts rather than answering for address(0). Any off-chain or on-chain integrator that queries membership for an unset/zero address (a common default in frontends and other contracts) gets a revert instead of false. No funds are affected and no state path in OGPass itself reaches balanceOf(0) (claimFounder/buy use msg.sender, which cannot be zero), so this is informational. Fix: return account != address(0) && balanceOf(account) != 0, or read _balances via a try/catch-free path.","line":327,"path":"src/OGPass.sol","reproduction":"Call pass.isMember(address(0)). Expected: false. Actual: revert ERC721InvalidOwner(0x0000000000000000000000000000000000000000). Scratch test test/scratch/MathProbe.t.sol::test_isMemberZeroAddressReverts passes with vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InvalidOwner.selector, address(0))). The existing suite never queries isMember/balanceOf for address(0).","severity":"info","snippet":"        return balanceOf(account) != 0;","title":"isMember(address(0)) reverts with ERC721InvalidOwner instead of returning false"},{"citation":"resolved","description":"The brief limits the owner to 'pause/unpause NEW buys'. Because OGPass inherits Ownable2Step, the owner can also call renounceOwnership() (single-step, inherited from Ownable). If the owner calls setBuysPaused(true) and then renounceOwnership(), owner() becomes address(0) and nobody can ever call setBuysPaused(false): new public buys are frozen for the life of the contract while founder claims, sells and buybacks still work and curveReserve still drains to zero. This requires the owner to act, so under the validation gates it is not a vulnerability; it is recorded as a material privileged-power risk the brief's 'owner can only pause/unpause' wording does not mention. Minimal fix preserving the design, if the requester wants the guarantee: override renounceOwnership() to revert, or to require !buysPaused.","line":289,"path":"src/OGPass.sol","reproduction":"State: fresh deployment. Steps: (1) owner calls setBuysPaused(true); (2) owner calls renounceOwnership(); (3) any user with 1,000,000 OG approved calls buy(1_000_000e18, block.timestamp). Expected per brief: the pause is a temporary switch the owner can lift. Actual: step 3 reverts BuysPaused() forever, and setBuysPaused(false) from any address reverts OwnableUnauthorizedAccount. Scratch test test/scratch/MathProbe.t.sol::test_renounceWhilePausedIsPermanent passes. The existing suite never exercises renounceOwnership.","severity":"info","snippet":"        buysPaused = paused;","title":"Trust assumption: renouncing ownership while buys are paused makes the buy pause permanent (no unprivileged amplifier; documented as owner-power risk)"}],"hash":"0caf1271114a31956626d6925eea5056974b85b6e81aed8b7d15d35da81c2ccc","nodeId":"6c85fda3-d787-44f5-aed0-57ea130f99da","outcome":"completed","summary":"The findings file is written and validated: all three snippets match the tree, and every one of the 14 entry points has a coverage row (20 rows total, including the invariants checked). No tracked files were changed; the only additions are the findings file and my scratch probe under `test/scratch/`.\n\n**Recap**\n\nThe math area is clean. The curve arithmetic is exact everywhere: every price is a multiple of 10,000 OG, so the 92.5 / 7.5 split leaves zero dust at all 1,000 in-range prices, the curve reserve equals the sum of outstanding prices by induction, and no intermediate can overflow. The supply-cap check keeps outstanding passes at or below 1,000 in every sequence, and the deadline boundary is consistent across claims, reserved slots and the reserve burn. The vendored OpenZeppelin v5.0.2 SafeERC20 rejects a no-code token, and I confirmed on mainnet that OG is a plain 18-decimal ERC-20 with no external calls, owner, or pause, and that it already treats dEaD as its burn sink.\n\nThree findings were recorded, none with a proof file since none is high severity:\n\n- **Low: curve parameters versus OG supply.** Filling 951 public passes needs about 5.47 billion OG, but OG's total supply is 1 billion. At most 347 public passes can ever exist with circulating OG, so the 1,000 cap and the `SupplyCapReached` branch are unreachable on mainnet. The code matches the brief literally, so this is flagged for the requester to confirm the intended prices before the immutable constants ship.\n- **Info: `isMember(address(0))` reverts** instead of returning false, because OpenZeppelin's `balanceOf` rejects the zero address.\n- **Info: renouncing ownership while buys are paused** makes the pause permanent. This needs owner action, so it is recorded as a trust-assumption note rather than a vulnerability.\n\nCoverage gaps: none in my area. Static-analysis leads were all checked: the reentrancy lead on the balance-delta pull is protected by the guard and CEI ordering, the timestamp leads are intended deadline logic, and the \"ETH send\" lead is a false positive on `safeTransfer` of an ERC-20.","treeHash":null,"usage":{"cachedInputTokens":1233397,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":28927,"runtime":"claude","turns":32,"wallClockMs":434645}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"764cd99b4c628a47","findings":[],"hash":"0ed8127008799bd81e802e8342e5276201d60c47817646209cb71583b395bed6","nodeId":"1b28b5b1-26ac-4b83-85b1-02f18057cd2c","outcome":"failed","summary":"Not logged in · Please run /login","treeHash":null,"usage":{"cachedInputTokens":256795,"inputTokens":130,"model":"claude-fable-5-1","outputTokens":11905,"runtime":"claude","turns":17,"wallClockMs":207296}},{"artifacts":[],"attempt":1,"bundleHash":"7c55749328ba53cd9749ac24c51c3b8b6076307b312ee230864540d41c055797","device":"3f91b58cf7cd2d45","findings":[],"hash":"25d7c8af1f7f2469209c40feb798a0e50714e3d74af530f20452a9feded4855e","nodeId":"1b28b5b1-26ac-4b83-85b1-02f18057cd2c","outcome":"completed","summary":"All 115 tests pass across the full suite, with the build clean and only three new files under `test/` in the working tree.\n\n**What I found about the contract**\n\nNo defect to report. I read `src/OGPass.sol` as an adversary and probed every path the brief names: soulbound overrides, the Merkle leaf format, the cap formula, the deadline boundary, the 92.5/7.5 split, reserve separation, and the owner's powers. Each held, including under six deliberately injected bugs that I temporarily applied and reverted to confirm the new tests are not tautological. Every mutation was caught by at least one new test. No `.imd-findings.json` was written because nothing qualifies as a defect.\n\n**What I added**, building on the existing 79-test suite rather than rewriting it:\n\n- `test/OGPassAdversarial.t.sol` (35 tests). Hidden-privilege probes over 34 plausible owner selectors plus fallback and ETH rejection. Burn-by-transfer to the zero address, self-transfer, and transfer to a willing ERC-721 receiver all refused. Hostile payment tokens: over-delivering, false-returning, and USDT-style no-return, plus a dead token address that blocks buys but never claims. Tampered, truncated, extended and reordered Merkle proofs, a one-leaf tree with an empty proof, founder count of zero and of the full cap, and a fuzz grounding the test Merkle helper against OpenZeppelin's verifier. Duplicate sells, token id zero, burned ids, inclusive deadlines, zero max price. The full 1,000-pass lifecycle filled to the cap and unwound to a zero balance in two orders, and a preview-equals-execution round-trip fuzz showing no seller ever profits.\n- `test/OGPassHostile.invariant.t.sol`. A second handler that asserts the exact revert on every failure path under random state: transfers, stranger claims, double buys, founder passes into the curve, owner withdrawal attempts, expired and badly priced orders. It also pushes stray OG in directly, lets founders buy and sell public passes, crosses the claim deadline, and toggles the pause. Nine invariants cover reserve equals sum of outstanding prices, exact wei conservation including donations, a token registry reconciled to the counters, cap and founder counters, claims frozen after the deadline, one pass per wallet, and quote consistency. An `afterInvariant` hook unwinds everything and checks the contract ends holding exactly the stray donations.\n- `test/mocks/HostileOG.sol` with the hostile token and receiver mocks.\n\n**Two harness bugs of my own** surfaced during the run and were fixed before the final pass: a prank consumed by a view call, and an unchecked `+ 1` on a fuzzed `uint256`. Both were caught by the exact-revert assertions, which is the point of them.\n\nNothing is committed. The source and all configuration files are untouched.","treeHash":"bc6abc098baafd835eb89384728671363ee09967","usage":{"cachedInputTokens":2256731,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":51269,"runtime":"claude","turns":33,"wallClockMs":1075102}},{"artifacts":[],"attempt":1,"bundleHash":"9a493a74575784792bf5ff0a7c0d140282f97030f2ccae1dd16c843868dd2f47","device":"2dc755dfe7bd177c","findings":[],"hash":"5de0d2080e0fdb68c9f56795be111c4fcfa91654a487df2e70a4bd139ac3fb5d","nodeId":"701ce5d6-0478-4f40-b7b0-a1b8847a87b4","outcome":"completed","summary":"The OGPass project is complete, verified, and committed on `main` (commit `926f674`). Working tree is clean.\n\n**What was built**\n\n- `src/OGPass.sol`: soulbound ERC-721 (\"OG Pass\" / \"OGPASS\") paid in the OG token. Founder passes are claimed by Merkle proof (OpenZeppelin standard leaf) before an immutable 30-day deadline, sit off-curve, and can be burned for exactly 100,000 OG from a separately funded founder reserve. Public passes mint on the linear curve `P(n) = 1,000,000 + 10,000·(n−1)` OG and sell for 92.5 % of `P(publicSupply)` with 7.5 % sent to dEaD. The curve reserve always equals the sum of outstanding prices. Every transfer and approval reverts; one pass per wallet. The owner can only set the base URI and pause new buys; ownership handover is two-step. No withdraw path, no proxy.\n- Supply cap of 1,000: unclaimed founder slots stay reserved until the deadline, so the public can take at most 951 before then. Burned passes of either kind free a slot.\n- Safety: `ReentrancyGuard`, checks-effects-interactions, `_mint` instead of `_safeMint`, every deposit verified by balance delta, `SafeERC20`, custom errors, all requested events. The constructor never touches the token, so it deploys on an empty chain.\n- `script/DeployOGPass.s.sol` holds the mainnet parameters (OG token, the given root, 49, the `$owner` address) in constants with a `deploy(Config)` function the tests call.\n- `README.md` documents behaviour, assumptions, deployment parameters, and the operator's after-launch steps.\n\n**Verification results**\n\n| Check | Result |\n| --- | --- |\n| `forge build` (solc 0.8.26, `bytecode_hash = \"none\"`) | pass |\n| `forge test` | 79 tests pass: 67 unit, 8 fuzz at 256 runs, 6 invariants over 64 runs × 64 depth with `fail_on_revert`, 3 deploy-config |\n| `forge fmt --check` | pass |\n| Scratch replica of the protected opcode/size check | runtime 9,937 bytes, no DELEGATECALL / CALLCODE / SELFDESTRUCT |\n\nTests cover soulbound reverts, valid/invalid/double/late Merkle claims, buy and sell slippage and deadlines, random buy/sell/claim/buyback sequences with `curveReserve == Σ P(i)` and balance-equals-reserves after every step, any-order sell-out emptying the curve, founder buyback accounting, `burnUnclaimed` math including repeat calls, the 951/49 cap and slot freeing, one-pass-per-wallet, pause blocking only buys, fee-on-transfer rejection, and a hostile reentrant token.\n\n**Things to know**\n\n- OpenZeppelin v5.0.2 (full `contracts/` minus mocks) and forge-std v1.9.6 are vendored as plain files with provenance notes; the OZ archive hash matches the copy already used elsewhere on this machine.\n- The OG token address read back `symbol() = \"OG\"`, `decimals() = 18` from a public mainnet RPC today. The no-fee property is taken from the brief, and the contract rejects any shortfall regardless.\n- At the exact deadline timestamp, claims are closed and the reserve burn is open.\n- Founder buybacks only work if someone funds the reserve; nothing in the contract obliges anyone to. OG sent to the contract by plain transfer is unrecoverable by design, since the owner has no withdrawal power.\n- Slither and Mythril were not available and did not run. No `launch.json` was written; the manifest step owns that.","treeHash":"998b5a699ea2eab70a9a64b19311c60f455bc0ea","usage":{"cachedInputTokens":1811557,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":63071,"runtime":"claude","turns":43,"wallClockMs":710248}},{"artifacts":[],"attempt":1,"bundleHash":"76307295d8989bf41f5e11101bebd36ecfe602d4befc1f988c096e7cf300bc4b","device":"3beb94a990617d46","findings":[],"hash":"a368356ceb1ac2d76af163b26844bdacd666cf5e5577f57cb33833c9d5c04f90","nodeId":"23a982c2-9345-4d02-aee4-06e05ce0986e","outcome":"completed","summary":"Wrote `launch.json` for the single accepted contract. Build and all 79 Foundry tests pass, and only the manifest is a new file in the tree.\n\n**Manifest contents**\n- kind is evm_contracts with one contract, OGPass, in `src/OGPass.sol:153`.\n- Constructor arguments in declaration order: the OG token address, the founder Merkle root from the brief, founderCount 49, and `$owner` for the owner that may set the base URI and pause buys.\n- Notes describe the soulbound pass, the curve pricing, the founder reserve and buyback, the 1,000 cap, and the owner's limited powers, at 1,119 characters.\n\n**Checks done**\n- Confirmed the argument types are static ABI words: address, bytes32, uint256, and an owner reference, matching the README deployment table and the deploy script constants.\n- Verified the file has exactly the three schema keys, every argument is a string, and the name is under 32 characters.\n- The constructor makes no external call, so it deploys on the empty-chain rehearsal in the protected test.\n\nNo other file was changed. The working tree shows only the untracked manifest, which I did not commit since the brief did not ask for a commit.","treeHash":"ecb3e65e3a0360a84e7b2b8f2d90469a71640681","usage":{"cachedInputTokens":190909,"inputTokens":162,"model":"claude-fable-5-1","outputTokens":2536,"runtime":"claude","turns":6,"wallClockMs":56104}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"93c37f17670e4d98","findings":[{"citation":"resolved","description":"Merged from audit_math, audit_economics, audit_flow and audit_permissions (same root cause). The brief says the owner can only setBaseURI and pause/unpause NEW buys. OGPass inherits Ownable2Step without overriding renounceOwnership(), so the owner also has a single-step renounce. If the owner calls setBuysPaused(true) and then renounceOwnership() (mistake or compromised key), owner() becomes address(0) and setBuysPaused is onlyOwner, so no address can ever clear buysPaused: buy() reverts BuysPaused() for the life of the contract. claimFounder, sell, sellFounder, fundFounderReserve and burnUnclaimedFounderReserve keep working, so no OG is locked and every holder can still exit; the curve can only shrink. The owner can already pause indefinitely, so this only removes the recovery path. It is a trust assumption on the owner key rather than a permission bypass, but the brief's enumeration of owner powers is not exhaustive as written. The repository's adversarial suite (test/OGPassAdversarial.t.sol, test_renouncedOwnerFreezesPauseStateButNeverExits) already exercises exactly this sequence and treats it as accepted behaviour, and README.md documents 'the owner may also renounceOwnership'. Minimal fix that preserves the design, if the requester wants the two listed powers to be exhaustive: override renounceOwnership() to revert, or to require !buysPaused. Otherwise record it as a documented owner-key trust assumption.","line":30,"path":"src/OGPass.sol","reproduction":"Deploy OGPass(og, root, 49, owner). vm.startPrank(owner); pass.setBuysPaused(true); pass.renounceOwnership(); vm.stopPrank(); assertEq(pass.owner(), address(0)). Alice holds 1,000,000e18 OG approved to the pass and calls pass.buy(type(uint256).max, block.timestamp). Expected per brief (pause is a reversible owner switch): some party can lift the pause. Actual: buy reverts BuysPaused(); owner calling setBuysPaused(false) reverts OwnableUnauthorizedAccount(owner); no address can ever unpause. Reproduced in test/scratch/Repro.t.sol::test_renounceWhilePaused (passes on current code with those expectRevert assertions).","severity":"low","snippet":"contract OGPass is ERC721, Ownable2Step, ReentrancyGuard {","title":"Inherited renounceOwnership() is an owner power the brief does not list; renouncing while buys are paused makes the pause permanent"},{"citation":"resolved","description":"Merged from audit_math, audit_flow and audit_permissions. isMember is documented as a plain boolean view ('Whether account currently holds a pass') but forwards to OpenZeppelin v5 ERC721.balanceOf, which reverts with ERC721InvalidOwner(address(0)) for the zero address. Any frontend or integrating contract that probes membership for an unset/zero address (a common default for a disconnected wallet) gets a revert instead of false. No funds, permissions or internal state paths are affected: claimFounder and buy call balanceOf(msg.sender), which is never zero. Interface-guarantee gap only. Minimal fix: return account != address(0) && balanceOf(account) != 0;. The existing suite never queries isMember or balanceOf for address(0).","line":327,"path":"src/OGPass.sol","reproduction":"Deploy OGPass with any arguments. Call pass.isMember(address(0)). Expected: false. Actual: revert ERC721InvalidOwner(0x0000000000000000000000000000000000000000). Reproduced in test/scratch/Repro.t.sol::test_isMemberZero with vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InvalidOwner.selector, address(0))), which passes on the current code.","severity":"low","snippet":"        return balanceOf(account) != 0;","title":"isMember(address(0)) reverts with ERC721InvalidOwner instead of returning false"},{"citation":"resolved","description":"From audit_math; reproduced by arithmetic and by reading the live token. The contract implements the brief's prices literally: P(n) = 1,000,000 OG + 10,000 OG * (n - 1). Holding 951 public passes requires curveReserve = sum P(1..951) = 5,468,250,000 OG. The OG token at 0xce7eb1ad9e2e1c784ea05f7ea4a0fe625923d10a, read on mainnet on 2026-10-11 via cast, reports totalSupply() = 1,000,000,000e18 and balanceOf(0xdEaD) = 52,689,865.7e18. Even if every OG in existence were deposited into the curve, publicSupply can reach at most 358 (sum P(1..358) = 997,030,000 OG), and at most 347 once the already-burned dEaD balance is excluded. Consequences: the SupplyCapReached branch in buy() can never trigger on mainnet (buys fail earlier in OG.transferFrom for lack of balance), the 'up to 951 public' membership size is not achievable, the first pass alone costs 0.1% of all OG, and the 100,000 OG founder buyback is 10x cheaper than the cheapest public pass. The code matches the brief, so this is not a code defect; it is reported because BASE_PRICE and PRICE_STEP are immutable constants the owner cannot change after deployment, so if the intended units differ (e.g. 1,000 OG + 10 OG per step) they must be corrected before launch. A fork test of buy/sell/fundFounderReserve/burnUnclaimedFounderReserve against the real token (test/scratch/ForkOG.t.sol) otherwise passed: no fee, no wallet limit, transfers to dEaD succeed.","line":44,"path":"src/OGPass.sol","reproduction":"Pure arithmetic, no state needed: sum over n = 1..951 of (1_000_000e18 + 10_000e18 * (n - 1)) = 5_468_250_000e18 > 1e27 = OG.totalSupply(); the largest n with sum P(1..n) <= 1e27 is 358. Reproduced in test/scratch/Repro.t.sol::test_curveCapacity (assertEq(sum, 5_468_250_000e18); assertEq(maxN, 358)). Expected per brief: public supply can reach 951 and buy() then reverts SupplyCapReached. Actual on mainnet: publicSupply cannot exceed 358 for any distribution of OG, so the cap check at src/OGPass.sol:240 is unreachable with the real token.","severity":"info","snippet":"    uint256 public constant BASE_PRICE = 1_000_000e18;","title":"Curve constants exceed the live OG token's total supply: the 951-public / 1,000-pass cap is unreachable on mainnet (parameter consistency for the requester to confirm)"},{"citation":"resolved","description":"From audit_flow; reproduced. After claimDeadline no new founder can claim, so the only useful deposit is founderOutstanding * FOUNDER_BUYBACK - founderReserve. fundFounderReserve does not bound the deposit after the deadline (nor refuse it when founderOutstanding == 0), while burnUnclaimedFounderReserve is permissionless and burns every wei above what outstanding founders need. A donor who deposits more than the shortfall after the deadline loses the excess to dEaD to any caller. This is the behaviour the brief specifies ('anyone can call burnUnclaimedFounderReserve() which sends the reserve not needed for still-held founder buybacks to dEaD') and README.md documents it under operational responsibilities, so it is recorded as a design note rather than a defect. If the requester wants a guard against donor mistakes without changing the economics: after the deadline, revert when founderOutstanding == 0 and cap amount so founderReserve + amount <= founderOutstanding * FOUNDER_BUYBACK.","line":208,"path":"src/OGPass.sol","reproduction":"Deploy OGPass; nobody claims; vm.warp(pass.claimDeadline()). Donor approves and calls pass.fundFounderReserve(100_000e18): succeeds, founderReserve == 100_000e18. Any unrelated address calls pass.burnUnclaimedFounderReserve(): returns 100_000e18, founderReserve == 0, OG.balanceOf(0xdEaD) == 100_000e18. Reproduced in test/scratch/Repro.t.sol::test_postDeadlineDepositBurned (passes on current code).","severity":"info","snippet":"    function fundFounderReserve(uint256 amount) external nonReentrant {","title":"fundFounderReserve accepts post-deadline deposits beyond what held founders need, and any caller can burn the excess in the next block"},{"citation":"resolved","description":"From audit_permissions; reproduced. The cap check bounds publicSupply + founderOutstanding + reservedFounderSlots, where _reservedFounderSlots() (src/OGPass.sol:363-366) returns 0 from claimDeadline on and founderOutstanding drops when a founder calls sellFounder. The public side is therefore not capped at 951: after the deadline the public can mint all 1,000 slots, and even before it a founder buyback frees a slot that a public buyer takes (952 public + 48 reserved founder). The 1,000 total always holds, curveReserve stays equal to the sum of outstanding prices, and no funds are at risk. The brief's 'Burned passes free up room' supports the pre-deadline reading, and README.md ('From the deadline on, unclaimed founder slots open to the public') documents the post-deadline reading as intended. Reported only so the requester can confirm; if '49 founder + up to 951 public' is meant as a hard split, buy() needs an additional publicSupply >= MAX_SUPPLY - founderCount guard.","line":240,"path":"src/OGPass.sol","reproduction":"Deploy OGPass(og, root, 49, owner); nobody claims; vm.warp(pass.claimDeadline()). From 1,000 distinct funded wallets call buy(type(uint256).max, block.timestamp): all succeed, publicSupply() == 1000; the 1,001st reverts SupplyCapReached. Second input, before the deadline: founder F claims; 951 public buys succeed and the 952nd reverts SupplyCapReached; reserve funded with 100_000e18 and F calls sellFounder; the same buyer's buy now succeeds, publicSupply() == 952 while reservedFounderSlots() == 48. Reproduced in test/scratch/Repro.t.sol::test_publicExceeds951AfterDeadline and test_publicReaches952BeforeDeadlineAfterFounderSell (both pass on current code).","severity":"info","snippet":"        if (publicSupply + founderOutstanding + _reservedFounderSlots() >= MAX_SUPPLY) revert SupplyCapReached();","title":"Public passes can exceed 951 (up to 1,000) once the claim deadline passes or a founder sells back; only the 1,000 total is a hard cap"}],"hash":"a8480c9f7cab088480ee28f6a1ecf922e34578ad6cca3a88ff744190cdfa99b9","nodeId":"4d304301-23a1-4b05-acb5-281392d887fb","outcome":"completed","summary":"The findings file is in place and excluded from git by the harness, so the working tree is otherwise unchanged. The review is complete.\n\n**Outcome:** no critical, high or medium defects. The code matches the brief, and the live OG token behaves as the brief assumes. I kept five findings, all reproduced, with no proofs since none is high or critical.\n\n**What I verified**\n\n- All 115 existing tests pass, including both invariant suites.\n- A mainnet fork test ran the full flow against the real OG token. It is a plain ERC-20 with no owner, pause, proxy, fee or wallet limit. Buy, fund, sell, and burns to the dead address all moved exact amounts.\n- Every specialist finding was rerun as a scratch Foundry test and behaved as reported. Static-analysis leads did not reproduce: the reentrancy line is guarded and the token has no callbacks, the \"ETH send\" line is a false positive, and timestamp use is the design.\n\n**Findings kept, after merging**\n\n1. **Low.** Inherited renounceOwnership while buys are paused makes the pause permanent. Four specialists reported this; merged into one. The adversarial suite already exercises the sequence and the README documents it, so it is a trust assumption the brief's owner-power list omits.\n2. **Low.** isMember(address(0)) reverts instead of returning false. Three specialists reported this; merged.\n3. **Info.** The curve constants need 5.47 billion OG to reach 951 public passes, but OG's total supply is 1 billion. The cap is unreachable on mainnet. The code follows the brief literally, so this is a parameter check for the requester before the immutable constants ship.\n4. **Info.** Post-deadline founder-reserve deposits beyond what held founders need can be burned by anyone. This is the brief's own rule; recorded as a design note.\n5. **Info.** Public passes can exceed 951 after the deadline or after a founder buyback. Only the 1,000 total is hard. The README documents this reading.\n\n**Coverage:** all 14 entry points answered, plus rows for the reserve and supply invariants, deployment constraints, and the live token. Eleven hold, three point at findings 1, 4 and 5.","treeHash":null,"usage":{"cachedInputTokens":1662709,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":29192,"runtime":"claude","turns":23,"wallClockMs":422040}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"91067b3439961e55","findings":[{"citation":"resolved","description":"isMember is documented as a plain boolean view (\"Whether `account` currently holds a pass\"), but it forwards to OpenZeppelin v5 ERC721.balanceOf, which reverts with ERC721InvalidOwner(address(0)) for the zero address. Any integrator or frontend that passes an unset/zero address (a common default) gets a revert instead of false. No funds are at risk; it is an interface-guarantee gap between what the view promises and what it does. Minimal fix: `return account != address(0) && balanceOf(account) != 0;`.","line":327,"path":"src/OGPass.sol","reproduction":"Deploy OGPass with any arguments. Call pass.isMember(address(0)). Expected: returns false. Actual: reverts with ERC721InvalidOwner(0x0000000000000000000000000000000000000000). Verified with a Foundry test using vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InvalidOwner.selector, address(0))) around pass.isMember(address(0)), which passes on the current code.","severity":"low","snippet":"        return balanceOf(account) != 0;","title":"isMember(address(0)) reverts instead of returning false"},{"citation":"resolved","description":"After claimDeadline no new founder can ever claim, so the only useful deposit is the shortfall founderOutstanding * FOUNDER_BUYBACK - founderReserve. fundFounderReserve does not bound the deposit after the deadline (nor refuse it when founderOutstanding == 0), while burnUnclaimedFounderReserve is permissionless and burns every wei above what outstanding founders need. A donor who deposits more than the shortfall after the deadline (for example the full 4,900,000 OG budget intended for 49 founders when only a few are still held, or any amount when none are held) loses the excess to dEaD in the next block to any caller, including a bot watching the mempool. The README documents the burn, but the deposit path itself has no guard against the mistake, which is an irreversible loss of the donor's OG with no benefit to anyone. Minimal fix that preserves the design: after the deadline, revert when founderOutstanding == 0 and cap `amount` (or revert) when founderReserve + amount would exceed founderOutstanding * FOUNDER_BUYBACK.","line":209,"path":"src/OGPass.sol","reproduction":"State: deploy OGPass; nobody claims; vm.warp(pass.claimDeadline()). Donor mints/approves 100_000e18 OG and calls pass.fundFounderReserve(100_000e18) -> succeeds, founderReserve == 100_000e18. Any unrelated address then calls pass.burnUnclaimedFounderReserve() -> returns 100_000e18, founderReserve == 0, OG.balanceOf(0xdEaD) += 100_000e18. Expected: the deposit is refused (or capped to the shortfall, here 0) because it can never pay a founder. Actual: accepted and burned by a third party. Verified with a Foundry test on the current code.","severity":"low","snippet":"        if (amount == 0) revert ZeroAmount();\n        founderReserve += amount;","title":"fundFounderReserve accepts post-deadline deposits that anyone can immediately burn"},{"citation":"resolved","description":"Trust assumption, not a bypass: the brief says the owner can only set the base URI and pause/unpause new buys, but Ownable2Step also exposes transferOwnership/acceptOwnership (two-step, safe) and renounceOwnership. If the owner calls setBuysPaused(true) and then renounceOwnership() (by mistake or key compromise), buysPaused can never be cleared and buy() reverts BuysPaused forever. Sells, founder buybacks, claims and reserve burning keep working, so no OG is lost; the curve just can only shrink. The same key could already stop buys by pausing, so this only removes the recovery path. If permanent pausing is not acceptable, override renounceOwnership to revert (or to require !buysPaused); otherwise record it as an owner-key trust assumption.","line":30,"path":"src/OGPass.sol","reproduction":"vm.startPrank(owner); pass.setBuysPaused(true); pass.renounceOwnership(); vm.stopPrank(). Then pass.owner() == address(0); alice (funded and approved for 1_000_000e18) calls pass.buy(1_000_000e18, block.timestamp) -> reverts BuysPaused; the former owner calling setBuysPaused(false) reverts OwnableUnauthorizedAccount. Expected per brief: the owner can pause and unpause; actual: a paused state with no owner cannot be unpaused by anyone. Verified with a Foundry test on the current code.","severity":"info","snippet":"contract OGPass is ERC721, Ownable2Step, ReentrancyGuard {","title":"Inherited renounceOwnership can freeze public buys permanently when called while paused"}],"hash":"abbffad3be2f4114547a658791bc7b56cb64eddb5970090625e7da7c4fb30d32","nodeId":"d315da87-b994-4864-9c97-7ef02c1ff450","outcome":"completed","summary":"Review complete. The findings file is written and validated, scratch tests are removed, and the working tree is clean apart from the ignored `.imd-findings.json`.\n\n**Outcome: no critical, high or medium defects.** The contract's control flow holds under the Execution Trace, Periphery and First Principles checks. Three low-impact items are reported, each with a reproduction I confirmed in a Foundry test on the current code.\n\n**Findings written**\n\n1. **Low. `isMember(address(0))` reverts** with ERC721InvalidOwner instead of returning false, because it forwards to OpenZeppelin's `balanceOf`. Interface gap only.\n2. **Low. Post-deadline deposits are accepted and instantly burnable.** With no founder outstanding, a donor's deposit via fundFounderReserve is sent to dEaD by the next caller of burnUnclaimedFounderReserve. The burn is by design, but the deposit path has no guard against the mistake.\n3. **Info. Inherited `renounceOwnership`** called while buys are paused freezes buys forever. Sells, claims and buybacks keep working, so no OG is lost. Recorded as a trust assumption.\n\n**What I verified as holding**\n\n- Every entry point traced for checks, effects and failure paths: soulbound overrides, Merkle claim, cap reservation math, curve pricing and exact 92.5/7.5 split, founder reserve accounting, checks-effects-interactions with the guard. The existing 79 tests pass.\n- Periphery: the live OG token on mainnet is a plain ERC-20 with no pause, blacklist, fee or owner. I ran a mainnet-fork test of buy, fund, sell, founder buyback and reserve burn with the real token and every balance moved exactly. Transfers to dEaD succeed and are counted in the token's `totalBurned`.\n- Vendored OpenZeppelin is byte-identical to the v5.0.2 tarball. forge-std differs from v1.9.6 only by formatting, which is test-only. No submodules. Runtime bytecode has no DELEGATECALL, CALLCODE or SELFDESTRUCT.\n- Slither and aderyn leads were all false positives here: the reentrancy lead is behind the guard with state written first, the ETH-send lead is not ETH, and the timestamp uses are the intended deadline.\n\n**Open items I could not settle**\n\n- The founder Merkle root cannot be verified without the 49 addresses. If it was built with a different leaf encoding, no founder can claim, and the slots open to the public after 30 days.\n- The requested price curve needs about 5.47 billion OG to fill 951 public passes, but OG's total supply is 1 billion. At most around 358 public passes can ever be outstanding. That is a parameter choice in the brief, not a code defect.","treeHash":null,"usage":{"cachedInputTokens":1989851,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":35303,"runtime":"claude","turns":40,"wallClockMs":514523}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"523ef565dd740e25","findings":[{"citation":"resolved","description":"The cap check only bounds publicSupply + founderOutstanding + reservedFounderSlots. _reservedFounderSlots() (src/OGPass.sol:363-366) returns 0 from claimDeadline on, and founderOutstanding drops when a founder calls sellFounder, so the public side of the curve is not capped at 951. After the deadline the public can mint every one of the 1000 slots, and even before it a founder buyback frees a slot that a public buyer takes (952 public + 48 founder). The total of 1000 outstanding always holds, so no funds are at risk; the effect is that the curve can run to P(1000) = 10,990,000 OG instead of P(951) = 10,500,000 OG and the curve reserve can grow correspondingly larger than a '951 public' reading of the brief implies. README.md ('Supply cap') documents this as intentional: 'From the deadline on, unclaimed founder slots open to the public.' Reported so the requester can confirm that reading of the brief; if the public cap is meant to be a hard 951, the check needs a separate `publicSupply >= MAX_SUPPLY - founderCount` guard (asymmetry: the cap is enforced on the sum but not on the public class that the brief bounds). No change is needed if the README reading is the intended one.","line":240,"path":"src/OGPass.sol","reproduction":"Deploy OGPass(og, root, 49, owner). Nobody claims. vm.warp(pass.claimDeadline()). From 1000 distinct wallets call buy(type(uint256).max, block.timestamp) each with price approved. Expected under a strict '951 public' reading: the 952nd buy reverts SupplyCapReached. Actual: all 1000 succeed, publicSupply() == 1000, totalSupply() == 1000, the 1001st buy reverts SupplyCapReached. Second input, before the deadline: founder F claims; 951 public buys; F calls sellFounder (reserve funded with 100,000e18); one more buy succeeds -> publicSupply() == 952 while 48 founder slots remain reserved. Verified with a scratch Foundry test (test removed; not a proof since severity is low).","severity":"low","snippet":"        if (publicSupply + founderOutstanding + _reservedFounderSlots() >= MAX_SUPPLY) revert SupplyCapReached();","title":"Public passes can exceed 951 (up to 1000) once the claim deadline passes or a founder sells back; brief states '49 founder + up to 951 public'"},{"citation":"resolved","description":"The brief says the owner can only setBaseURI and pause/unpause new buys. Ownable2Step also exposes renounceOwnership() (onlyOwner) which sets owner to address(0) and deletes any pending owner. If the owner first calls setBuysPaused(true) and then renounceOwnership(), buysPaused can never be cleared: buy() reverts BuysPaused forever while claims, sells and founder buybacks keep working, so no funds are locked and every holder can still exit. This is an owner-only action and the brief already allows an indefinite buy pause, so it is a trust assumption rather than a permission bypass; it is recorded because the brief's enumeration of owner powers omits it and the test suite never exercises renounceOwnership. If the requester wants the enumerated powers to be exhaustive, override renounceOwnership to revert (or require !buysPaused); otherwise document it.","line":30,"path":"src/OGPass.sol","reproduction":"vm.startPrank(owner); pass.setBuysPaused(true); pass.renounceOwnership(); vm.stopPrank(); assertEq(pass.owner(), address(0)). Then any wallet with price approved calls buy(type(uint256).max, block.timestamp): reverts BuysPaused(). No address can call setBuysPaused(false) any more (onlyOwner with owner == 0). Verified with a scratch Foundry test.","severity":"info","snippet":"contract OGPass is ERC721, Ownable2Step, ReentrancyGuard {","title":"Inherited renounceOwnership is an owner power not listed in the brief; renouncing while buys are paused makes the pause permanent"},{"citation":"resolved","description":"OpenZeppelin v5 ERC721.balanceOf reverts with ERC721InvalidOwner for the zero address, so the isMember view inherits that revert. Any off-chain or on-chain consumer that probes isMember(address(0)) (for example a UI defaulting an unconnected wallet to the zero address) gets a revert rather than false. No funds or permissions are affected. Fix: `return account != address(0) && balanceOf(account) != 0;`.","line":327,"path":"src/OGPass.sol","reproduction":"Call pass.isMember(address(0)). Expected: false. Actual: revert ERC721InvalidOwner(0x0000000000000000000000000000000000000000). Verified with a scratch Foundry test.","severity":"info","snippet":"        return balanceOf(account) != 0;","title":"isMember(address(0)) reverts instead of returning false"}],"hash":"f451089d8691cd18bbf16544881868871e74ccc7127e4bdd39fc9e39bb3893be","nodeId":"d3914a4a-ea36-464d-87dd-bd88150726c4","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root and the scratch tests are removed, so no source or test file changed.\n\n**Outcome:** the permission model of OGPass holds. Nothing in the Access Control, Trust Gap or Asymmetry area reaches medium severity. Three low/info observations are recorded, each with a concrete input, and all 14 entry points plus six invariants have coverage rows.\n\n**What I checked**\n\n- Every state-changing entry point, including the inherited ERC-721 transfer and approval paths and the Ownable2Step handover. Transfers and approvals revert on every route, and the self-burn route through a zero-address transfer is blocked before the soulbound override, so no pass can leave or be burned except through `sell` or `sellFounder`.\n- Confused-deputy exposure. The contract only ever pulls OG from the caller, so a third party's standing approval cannot be spent by anyone else.\n- Storage-write symmetry between buy and sell, and between claim and founder buyback. The curve reserve and the founder reserve are kept strictly apart, and the owner has no withdrawal path.\n- The trust seams. The owner's pause touches only new buys and redirects no in-flight value. Sandwiching the curve is unprofitable unless at least nine later buyers follow, which is the curve's intended early-buyer economics rather than an extraction path.\n- The live OG token. I fetched its verified source from Sourcify and confirmed symbol and decimals over a public RPC. It is a plain ERC-20 with no fee, pause or blacklist, and it accepts transfers to the burn address, so the seller payout can never be blocked by the burn leg.\n- The static-analysis leads. The reentrancy, ETH-send and unsafe-mint lines do not reproduce against this code.\n\n**Recorded observations**\n\n1. Low: after the claim deadline, or after a founder buyback, public passes can exceed 951 and reach the full 1000. The README documents this as intentional, but the brief's wording \"up to 951 public\" leaves room for a stricter reading, so I flagged it for the requester to confirm.\n2. Info: the inherited `renounceOwnership` is an owner power the brief does not list. Pausing buys and then renouncing makes the pause permanent, though every holder can still exit.\n3. Info: `isMember(address(0))` reverts instead of returning false.\n\n**Not reached in depth:** the arithmetic of the price sum beyond the existing fuzz and invariant suites, and the launch manifest, which does not yet exist in the tree.","treeHash":null,"usage":{"cachedInputTokens":774413,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":24220,"runtime":"claude","turns":26,"wallClockMs":379692}}],"verification":[{"checks":[{"durationMs":3506,"exitCode":0,"name":"build","output":"Compiling 52 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.38s\nCompiler run successful!\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:184:9\n    │\n184 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:172:13\n    │\n172 │         if (block.timestamp >= claimDeadline) revert ClaimClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OGPass.sol:186:9\n    │\n186 │         emit Claim(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:218:13\n    │\n218 │         if (block.timestamp < claimDeadline) revert ClaimStillOpen();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:248:9\n    │\n248 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:237:13\n    │\n237 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:259:13\n    │\n259 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:364:13\n    │\n364 │         if (block.timestamp >= claimDeadline) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OGPassAdversarial.t.sol:445:27\n    │\n445 │         p.buy(BASE_PRICE, block.timestamp);\n    │                           ━━━━━━━━━━━━━━━\n446 │         vm.warp(p.claimDeadline());\n    │         ────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OGPassHostile.invariant.t.sol:211:17\n    │\n211 │         vm.warp(block.timestamp + delta);\n    │         ────────━━━━━━━━━━━━━━━───────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":8652,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/DeployOGPass.t.sol:DeployOGPassTest\n[PASS] test_deployOnEmptyChain() (gas: 2260730)\n[PASS] test_mainnetConfigMatchesBrief() (gas: 10361)\n[PASS] test_runtimeFitsEip170() (gas: 2176284)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 549.98µs (255.17µs CPU time)\n\nRan 35 tests for test/OGPassAdversarial.t.sol:OGPassAdversarialTest\n[PASS] testFuzz_approvalsNeverSettable(address,uint256,bool) (runs: 256, μ: 325581, ~: 325581)\n[PASS] testFuzz_burnKeepsExactlyWhatOutstandingFoundersNeed(uint8,uint256,uint8) (runs: 256, μ: 3770943, ~: 2632829)\nLogs:\n  Bound result 17\n  Bound result 48344077661625379621136748\n  Bound result 8\n\n[PASS] testFuzz_everyFounderCanClaimInAnyOrder(uint256) (runs: 256, μ: 15335843, ~: 15335845)\n[PASS] testFuzz_lateSellerNeverDrainsMoreThanReserveHolds(uint8,uint8) (runs: 256, μ: 4494978, ~: 2527689)\nLogs:\n  Bound result 46\n  Bound result 34\n\n[PASS] testFuzz_merkleHelperAgreesWithOpenZeppelin(uint8,uint8,uint256) (runs: 256, μ: 83560, ~: 65068)\nLogs:\n  Bound result 18\n  Bound result 1\n\n[PASS] testFuzz_quotesMatchExecution(uint8) (runs: 256, μ: 4405887, ~: 2679398)\nLogs:\n  Bound result 44\n\n[PASS] testFuzz_tamperedProofElementRejected(uint8,uint8,bytes32) (runs: 256, μ: 214312, ~: 214436)\nLogs:\n  Bound result 11\n  Bound result 4\n\n[PASS] testFuzz_truncatedOrExtendedProofRejected(uint8,bytes32) (runs: 256, μ: 289422, ~: 289424)\nLogs:\n  Bound result 34\n\n[PASS] test_buyWithInsufficientBalanceButEnoughAllowanceReverts() (gas: 304519)\n[PASS] test_buyWithZeroMaxPriceAlwaysReverts() (gas: 170695)\n[PASS] test_cannotBurnByTransferringToZero() (gas: 402784)\n[PASS] test_deadTokenAddressBlocksBuysButNotClaims() (gas: 545743)\n[PASS] test_directDonationIsNotCountedInAnyReserve() (gas: 884567)\n[PASS] test_falseReturningTokenRejected() (gas: 837165)\n[PASS] test_founderClaimAfterBurnStillImpossibleEvenWithReserveLeft() (gas: 992149)\n[PASS] test_founderCountEqualsCap_publicLockedOutUntilDeadline() (gas: 409632)\n[PASS] test_fullCapLifecycleUnwindsToZero() (gas: 350726259)\n[PASS] test_noHiddenPrivilegedFunctions() (gas: 1401718)\n[PASS] test_noReturnTokenAccepted() (gas: 768041)\n[PASS] test_overDeliveringTokenRejected() (gas: 998250)\n[PASS] test_pauseDoesNotBlockReserveFundingOrBurn() (gas: 677989)\n[PASS] test_pendingOwnerHasNoPowerUntilAccepted() (gas: 164500)\n[PASS] test_pricePins() (gas: 67095)\n[PASS] test_publicFillsAllThousandAfterDeadlineWhenNoFounderClaims() (gas: 319470105)\n[PASS] test_renouncedOwnerFreezesPauseStateButNeverExits() (gas: 1554668)\n[PASS] test_reserveFundedEventReportsRunningTotal() (gas: 278589)\n[PASS] test_safeTransferToWillingContractStillReverts() (gas: 516324)\n[PASS] test_selfTransferReverts() (gas: 356380)\n[PASS] test_sellDeadlineInclusiveAndFarFuture() (gas: 738608)\n[PASS] test_sellFounderTwiceReverts() (gas: 658765)\n[PASS] test_sellTwiceReverts() (gas: 446936)\n[PASS] test_singleLeafTree_emptyProofIsValid() (gas: 292184)\n[PASS] test_tokenIdZeroAndBurnedIdsRejectedEverywhere() (gas: 998737)\n[PASS] test_transferOfNonexistentTokenReverts() (gas: 47385)\n[PASS] test_zeroFounderCount_noClaimsAndFullPublicCap() (gas: 276292142)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 1.09s (5.84s CPU time)\n\nRan 67 tests for test/OGPass.t.sol:OGPassTest\n[PASS] test_burnUnclaimed_math() (gas: 1335854)\n[PASS] test_burnUnclaimed_noFoundersBurnsEverything() (gas: 293613)\n[PASS] test_burnUnclaimed_nothingToBurnWhenEmpty() (gas: 44571)\n[PASS] test_burnUnclaimed_nothingToBurnWhenExactlyCovered() (gas: 576086)\n[PASS] test_burnUnclaimed_nothingToBurnWhenUnderfunded() (gas: 772433)\n[PASS] test_burnUnclaimed_repeatableAfterNewFunding() (gas: 1178748)\n[PASS] test_burnUnclaimed_revertsBeforeDeadline() (gas: 246910)\n[PASS] test_buy_deadlineInclusive() (gas: 299626)\n[PASS] test_buy_firstPass() (gas: 388939)\n[PASS] test_buy_pricesFollowCurve() (gas: 5069084)\n[PASS] test_buy_pullsExactlyPriceEvenWithHigherMaxAndAllowance() (gas: 319676)\n[PASS] test_buy_rejectsFeeOnTransferToken() (gas: 920598)\n[PASS] test_buy_revertsAboveMaxPrice() (gas: 170941)\n[PASS] test_buy_revertsPastDeadline() (gas: 158947)\n[PASS] test_buy_revertsWhenPaused() (gas: 210836)\n[PASS] test_buy_revertsWithoutAllowance() (gas: 233248)\n[PASS] test_buy_slippageAfterFrontRun() (gas: 450352)\n[PASS] test_cap_publicStopsAtNineFiftyOneWhileFoundersReserved() (gas: 229073878)\n[PASS] test_cap_unclaimedFounderSlotsOpenAfterDeadline() (gas: 228718519)\n[PASS] test_claimFounder_allFortyNine() (gas: 9855258)\n[PASS] test_claimFounder_capFromFounderCount() (gas: 372994)\n[PASS] test_claimFounder_deadlineBoundary() (gas: 723074)\n[PASS] test_claimFounder_double() (gas: 413670)\n[PASS] test_claimFounder_doubleAfterBuyback() (gas: 693592)\n[PASS] test_claimFounder_invalidProof_empty() (gas: 46929)\n[PASS] test_claimFounder_invalidProof_nonFounder() (gas: 196809)\n[PASS] test_claimFounder_invalidProof_wrongLeaf() (gas: 198696)\n[PASS] test_claimFounder_memberCannotClaim() (gas: 492842)\n[PASS] test_claimFounder_notPausable() (gas: 382118)\n[PASS] test_claimFounder_valid() (gas: 439470)\n[PASS] test_constructorDoesNotTouchTokenCode() (gas: 13181)\n[PASS] test_constructorRejectsFounderCountAboveCap() (gas: 10494)\n[PASS] test_constructorRejectsZeroOwner() (gas: 8616)\n[PASS] test_constructorRejectsZeroToken() (gas: 8344)\n[PASS] test_fundFounderReserve() (gas: 242384)\n[PASS] test_fundFounderReserve_rejectsFeeOnTransfer() (gas: 823610)\n[PASS] test_fundFounderReserve_zeroReverts() (gas: 34851)\n[PASS] test_metadataAndImmutables() (gas: 156033)\n[PASS] test_onePerWallet_buyTwice() (gas: 450783)\n[PASS] test_onePerWallet_founderCanBuyAfterBuyback() (gas: 898027)\n[PASS] test_onePerWallet_founderCannotBuy() (gas: 486383)\n[PASS] test_ownerCannotExtractReserves() (gas: 656816)\n[PASS] test_ownershipHandoverIsTwoStep() (gas: 149759)\n[PASS] test_pause_onlyOwnerAndOnlyBuys() (gas: 1838254)\n[PASS] test_sellFounder_doesNotTouchCurveReserve() (gas: 902462)\n[PASS] test_sellFounder_paysExactlyBuyback() (gas: 700306)\n[PASS] test_sellFounder_reserveNotSharedWithCurve() (gas: 667831)\n[PASS] test_sellFounder_revertsForPublicPass() (gas: 501533)\n[PASS] test_sellFounder_revertsNotOwner() (gas: 568683)\n[PASS] test_sellFounder_revertsWithoutReserve() (gas: 582127)\n[PASS] test_sellFounder_worksAfterDeadlineAndWhenPaused() (gas: 685057)\n[PASS] test_sell_everyOrderEmptiesReserve() (gas: 2473379)\n[PASS] test_sell_notPausable() (gas: 455050)\n[PASS] test_sell_payoutUsesCurrentSupplyNotPurchasePrice() (gas: 645644)\n[PASS] test_sell_reentrancyGuardHolds() (gas: 1119997)\n[PASS] test_sell_revertsBelowMinOut() (gas: 340107)\n[PASS] test_sell_revertsForFounderPass() (gas: 672787)\n[PASS] test_sell_revertsNotOwner() (gas: 368253)\n[PASS] test_sell_revertsPastDeadline() (gas: 332307)\n[PASS] test_sell_singlePass() (gas: 497280)\n[PASS] test_sell_slippageAfterSomeoneElseSells() (gas: 695897)\n[PASS] test_sell_thenBuyAgainSameWallet() (gas: 670298)\n[PASS] test_setBaseURI() (gas: 412972)\n[PASS] test_soulbound_founderPass() (gas: 486389)\n[PASS] test_soulbound_publicPass() (gas: 457776)\n[PASS] test_supportsInterface() (gas: 25617)\n[PASS] test_views() (gas: 653885)\nSuite result: ok. 67 passed; 0 failed; 0 skipped; finished in 1.09s (1.52s CPU time)\n\nRan 8 tests for test/OGPassFuzz.t.sol:OGPassFuzzTest\n[PASS] testFuzz_anySellOrderEmptiesCurve(uint8,uint256) (runs: 256, μ: 4625910, ~: 3688756)\nLogs:\n  Bound result 8\n\n[PASS] testFuzz_burnUnclaimedMath(uint8,uint8,uint256) (runs: 256, μ: 3688691, ~: 2334407)\nLogs:\n  Bound result 18\n  Bound result 1\n  Bound result 20000000000000000000000000\n\n[PASS] testFuzz_buyRespectsMaxPrice(uint256) (runs: 256, μ: 228069, ~: 170662)\n[PASS] testFuzz_claimWindow(uint256) (runs: 256, μ: 263204, ~: 206859)\nLogs:\n  Bound result 21341553\n\n[PASS] testFuzz_deadlines(uint256) (runs: 256, μ: 293770, ~: 388996)\n[PASS] testFuzz_randomSequencePreservesInvariants(uint256,uint8) (runs: 256, μ: 6959600, ~: 3860757)\nLogs:\n  Bound result 1\n\n[PASS] testFuzz_sellRespectsMinOut(uint256) (runs: 256, μ: 373139, ~: 399745)\n[PASS] testFuzz_strangerCannotClaimWithAnyFounderProof(address,uint8) (runs: 256, μ: 199370, ~: 199431)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 1.09s (3.52s CPU time)\n\nRan 1 test for test/OGPass.invariant.t.sol:OGPassInvariantTest\n[PASS]\nOGPassInvariantTest invariants:\n[PASS] invariant_conservation\n[PASS] invariant_curveReserveEqualsSumOfOutstandingPrices\n[PASS] invariant_founderReserveNeverExceedsBalance\n[PASS] invariant_onePassPerWallet\n[PASS] invariant_solvent\n[PASS] invariant_supplyCapAndFounderCounts\n OGPassInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| OGPassHandler | burnUnclaimed | 532   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | buy           | 519   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | claim         | 504   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | fundReserve   | 514   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sell          | 522   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sellFounder   | 482   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | togglePause   | 503   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | warp          | 520   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1318\n  Bound result 2377\n  Bound result 336457\n  Bound result 12\n  Bound result 7218\n  Bound result 1460\n  Bound result 7777\n  Bound result 31536000\n  Bound result 4\n  Bound result 150000000000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.62s (2.62s CPU time)\n\nRan 1 test for test/OGPassHostile.invariant.t.sol:OGPassHostileInvariantTest\n[PASS]\nOGPassHostileInvariantTest invariants:\n[PASS] invariant_balanceCoversReservesPlusStrayDonations\n[PASS] invariant_capAndFounderCounters\n[PASS] invariant_claimsFrozenAfterDeadline\n[PASS] invariant_curveNeverSpendsFounderReserve\n[PASS] invariant_curveReserveIsSumOfOutstandingPrices\n[PASS] invariant_everyWeiAccountedFor\n[PASS] invariant_onePassPerWallet\n[PASS] invariant_quotesConsistent\n[PASS] invariant_tokenRegistryMatchesCounters\n OGPassHostileInvariantTest invariants (runs: 96, calls: 7680, reverts: 0)\n\n╭----------------+-------------------------+-------+---------+----------╮\n| Contract       | Selector                | Calls | Reverts | Discards |\n+=======================================================================+\n| HostileHandler | attemptBadOrders        | 427   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | attemptFounderIntoCurve | 418   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | attemptOwnerAbuse       | 446   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | attemptStrangerClaim    | 435   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | attemptTransfer         | 430   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | burnUnclaimed           | 436   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | buy                     | 866   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | claim                   | 424   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | donate                  | 433   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | founderBuysPublic       | 381   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | founderSellsPublic      | 441   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | fundReserve             | 427   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | sell                    | 863   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | sellFounder             | 404   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | togglePause             | 402   | 0       | 0        |\n|----------------+-------------------------+-------+---------+----------|\n| HostileHandler | warp                    | 447   | 0       | 0        |\n╰----------------+-------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 113851\n  Bound result 20200\n  Bound result 3324\n  Bound result 879\n  Bound result 2941\n  Bound result 400000000000000000000000\n  Bound result 31536000\n  Bound result 220534\n  Bound result 40\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.56s (8.56s CPU time)\n\nRan 6 test suites in 8.57s (14.47s CPU time): 115 tests passed, 0 failed, 0 skipped (115 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"OGPass.acceptOwnership()\",\"OGPass.burnUnclaimedFounderReserve()\",\"OGPass.buy(uint256,uint256)\",\"OGPass.claimFounder(bytes32[])\",\"OGPass.fundFounderReserve(uint256)\",\"OGPass.renounceOwnership()\",\"OGPass.safeTransferFrom(address,address,uint256)\",\"OGPass.safeTransferFrom(address,address,uint256,bytes)\",\"OGPass.sell(uint256,uint256,uint256)\",\"OGPass.sellFounder(uint256)\",\"OGPass.setBaseURI(string)\",\"OGPass.setBuysPaused(bool)\",\"OGPass.transferFrom(address,address,uint256)\",\"OGPass.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":178,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployOGPass.s.sol\":43,\"src/OGPass.sol\":375,\"test/DeployOGPass.t.sol\":49,\"test/OGPass.invariant.t.sol\":202,\"test/OGPass.t.sol\":911,\"test/OGPassAdversarial.t.sol\":754,\"test/OGPassBase.sol\":85,\"test/OGPassFuzz.t.sol\":245,\"test/OGPassHostile.invariant.t.sol\":595,\"test/helpers/Merkle.sol\":61,\"test/mocks/HostileOG.sol\":82,\"test/mocks/MockOG.sol\":34},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"25d7c8af1f7f2469209c40feb798a0e50714e3d74af530f20452a9feded4855e","verifiedTreeHash":"bc6abc098baafd835eb89384728671363ee09967","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":2213,"exitCode":0,"name":"build","output":"Compiling 49 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.09s\nCompiler run successful!\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:184:9\n    │\n184 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:172:13\n    │\n172 │         if (block.timestamp >= claimDeadline) revert ClaimClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OGPass.sol:186:9\n    │\n186 │         emit Claim(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:218:13\n    │\n218 │         if (block.timestamp < claimDeadline) revert ClaimStillOpen();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:248:9\n    │\n248 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:237:13\n    │\n237 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:259:13\n    │\n259 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:364:13\n    │\n364 │         if (block.timestamp >= claimDeadline) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":2409,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/DeployOGPass.t.sol:DeployOGPassTest\n[PASS] test_deployOnEmptyChain() (gas: 2260730)\n[PASS] test_mainnetConfigMatchesBrief() (gas: 10361)\n[PASS] test_runtimeFitsEip170() (gas: 2176284)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 561.66µs (504.63µs CPU time)\n\nRan 8 tests for test/OGPassFuzz.t.sol:OGPassFuzzTest\n[PASS] testFuzz_anySellOrderEmptiesCurve(uint8,uint256) (runs: 256, μ: 4490748, ~: 2977125)\nLogs:\n  Bound result 24\n\n[PASS] testFuzz_burnUnclaimedMath(uint8,uint8,uint256) (runs: 256, μ: 3761792, ~: 2169473)\nLogs:\n  Bound result 35\n  Bound result 9\n  Bound result 632688696777679024164386\n\n[PASS] testFuzz_buyRespectsMaxPrice(uint256) (runs: 256, μ: 217984, ~: 170626)\n[PASS] testFuzz_claimWindow(uint256) (runs: 256, μ: 268950, ~: 206859)\nLogs:\n  Bound result 2\n\n[PASS] testFuzz_deadlines(uint256) (runs: 256, μ: 287438, ~: 388942)\n[PASS] testFuzz_randomSequencePreservesInvariants(uint256,uint8) (runs: 256, μ: 7371763, ~: 3412041)\nLogs:\n  Bound result 1\n\n[PASS] testFuzz_sellRespectsMinOut(uint256) (runs: 256, μ: 377808, ~: 399745)\n[PASS] testFuzz_strangerCannotClaimWithAnyFounderProof(address,uint8) (runs: 256, μ: 199407, ~: 199431)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 308.75ms (1.41s CPU time)\n\nRan 67 tests for test/OGPass.t.sol:OGPassTest\n[PASS] test_burnUnclaimed_math() (gas: 1335854)\n[PASS] test_burnUnclaimed_noFoundersBurnsEverything() (gas: 293613)\n[PASS] test_burnUnclaimed_nothingToBurnWhenEmpty() (gas: 44571)\n[PASS] test_burnUnclaimed_nothingToBurnWhenExactlyCovered() (gas: 576086)\n[PASS] test_burnUnclaimed_nothingToBurnWhenUnderfunded() (gas: 772433)\n[PASS] test_burnUnclaimed_repeatableAfterNewFunding() (gas: 1178748)\n[PASS] test_burnUnclaimed_revertsBeforeDeadline() (gas: 246910)\n[PASS] test_buy_deadlineInclusive() (gas: 299626)\n[PASS] test_buy_firstPass() (gas: 388939)\n[PASS] test_buy_pricesFollowCurve() (gas: 5069084)\n[PASS] test_buy_pullsExactlyPriceEvenWithHigherMaxAndAllowance() (gas: 319676)\n[PASS] test_buy_rejectsFeeOnTransferToken() (gas: 920598)\n[PASS] test_buy_revertsAboveMaxPrice() (gas: 170941)\n[PASS] test_buy_revertsPastDeadline() (gas: 158947)\n[PASS] test_buy_revertsWhenPaused() (gas: 210836)\n[PASS] test_buy_revertsWithoutAllowance() (gas: 233248)\n[PASS] test_buy_slippageAfterFrontRun() (gas: 450352)\n[PASS] test_cap_publicStopsAtNineFiftyOneWhileFoundersReserved() (gas: 229073878)\n[PASS] test_cap_unclaimedFounderSlotsOpenAfterDeadline() (gas: 228718519)\n[PASS] test_claimFounder_allFortyNine() (gas: 9855258)\n[PASS] test_claimFounder_capFromFounderCount() (gas: 372994)\n[PASS] test_claimFounder_deadlineBoundary() (gas: 723074)\n[PASS] test_claimFounder_double() (gas: 413670)\n[PASS] test_claimFounder_doubleAfterBuyback() (gas: 693592)\n[PASS] test_claimFounder_invalidProof_empty() (gas: 46929)\n[PASS] test_claimFounder_invalidProof_nonFounder() (gas: 196809)\n[PASS] test_claimFounder_invalidProof_wrongLeaf() (gas: 198696)\n[PASS] test_claimFounder_memberCannotClaim() (gas: 492842)\n[PASS] test_claimFounder_notPausable() (gas: 382118)\n[PASS] test_claimFounder_valid() (gas: 439470)\n[PASS] test_constructorDoesNotTouchTokenCode() (gas: 13181)\n[PASS] test_constructorRejectsFounderCountAboveCap() (gas: 10494)\n[PASS] test_constructorRejectsZeroOwner() (gas: 8616)\n[PASS] test_constructorRejectsZeroToken() (gas: 8344)\n[PASS] test_fundFounderReserve() (gas: 242384)\n[PASS] test_fundFounderReserve_rejectsFeeOnTransfer() (gas: 823610)\n[PASS] test_fundFounderReserve_zeroReverts() (gas: 34851)\n[PASS] test_metadataAndImmutables() (gas: 156033)\n[PASS] test_onePerWallet_buyTwice() (gas: 450783)\n[PASS] test_onePerWallet_founderCanBuyAfterBuyback() (gas: 898027)\n[PASS] test_onePerWallet_founderCannotBuy() (gas: 486383)\n[PASS] test_ownerCannotExtractReserves() (gas: 656816)\n[PASS] test_ownershipHandoverIsTwoStep() (gas: 149759)\n[PASS] test_pause_onlyOwnerAndOnlyBuys() (gas: 1838254)\n[PASS] test_sellFounder_doesNotTouchCurveReserve() (gas: 902462)\n[PASS] test_sellFounder_paysExactlyBuyback() (gas: 700306)\n[PASS] test_sellFounder_reserveNotSharedWithCurve() (gas: 667831)\n[PASS] test_sellFounder_revertsForPublicPass() (gas: 501533)\n[PASS] test_sellFounder_revertsNotOwner() (gas: 568683)\n[PASS] test_sellFounder_revertsWithoutReserve() (gas: 582127)\n[PASS] test_sellFounder_worksAfterDeadlineAndWhenPaused() (gas: 685057)\n[PASS] test_sell_everyOrderEmptiesReserve() (gas: 2473379)\n[PASS] test_sell_notPausable() (gas: 455050)\n[PASS] test_sell_payoutUsesCurrentSupplyNotPurchasePrice() (gas: 645644)\n[PASS] test_sell_reentrancyGuardHolds() (gas: 1119997)\n[PASS] test_sell_revertsBelowMinOut() (gas: 340107)\n[PASS] test_sell_revertsForFounderPass() (gas: 672787)\n[PASS] test_sell_revertsNotOwner() (gas: 368253)\n[PASS] test_sell_revertsPastDeadline() (gas: 332307)\n[PASS] test_sell_singlePass() (gas: 497280)\n[PASS] test_sell_slippageAfterSomeoneElseSells() (gas: 695897)\n[PASS] test_sell_thenBuyAgainSameWallet() (gas: 670298)\n[PASS] test_setBaseURI() (gas: 412972)\n[PASS] test_soulbound_founderPass() (gas: 486389)\n[PASS] test_soulbound_publicPass() (gas: 457776)\n[PASS] test_supportsInterface() (gas: 25617)\n[PASS] test_views() (gas: 653885)\nSuite result: ok. 67 passed; 0 failed; 0 skipped; finished in 375.71ms (797.96ms CPU time)\n\nRan 1 test for test/OGPass.invariant.t.sol:OGPassInvariantTest\n[PASS]\nOGPassInvariantTest invariants:\n[PASS] invariant_conservation\n[PASS] invariant_curveReserveEqualsSumOfOutstandingPrices\n[PASS] invariant_founderReserveNeverExceedsBalance\n[PASS] invariant_onePassPerWallet\n[PASS] invariant_solvent\n[PASS] invariant_supplyCapAndFounderCounts\n OGPassInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| OGPassHandler | burnUnclaimed | 497   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | buy           | 517   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | claim         | 514   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | fundReserve   | 486   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sell          | 520   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sellFounder   | 505   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | togglePause   | 532   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | warp          | 525   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 40960\n  Bound result 2684\n  Bound result 5001\n  Bound result 247102235973350969987722\n  Bound result 270500\n  Bound result 9658\n  Bound result 50000000000000000000000\n  Bound result 1352\n  Bound result 161448\n  Bound result 205646481576794658901161\n  Bound result 15562\n  Bound result 40960\n  Bound result 906\n  Bound result 5000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.33s (2.32s CPU time)\n\nRan 4 test suites in 2.33s (3.01s CPU time): 79 tests passed, 0 failed, 0 skipped (79 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"OGPass.acceptOwnership()\",\"OGPass.burnUnclaimedFounderReserve()\",\"OGPass.buy(uint256,uint256)\",\"OGPass.claimFounder(bytes32[])\",\"OGPass.fundFounderReserve(uint256)\",\"OGPass.renounceOwnership()\",\"OGPass.safeTransferFrom(address,address,uint256)\",\"OGPass.safeTransferFrom(address,address,uint256,bytes)\",\"OGPass.sell(uint256,uint256,uint256)\",\"OGPass.sellFounder(uint256)\",\"OGPass.setBaseURI(string)\",\"OGPass.setBuysPaused(bool)\",\"OGPass.transferFrom(address,address,uint256)\",\"OGPass.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":178,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployOGPass.s.sol\":43,\"src/OGPass.sol\":375,\"test/DeployOGPass.t.sol\":49,\"test/OGPass.invariant.t.sol\":202,\"test/OGPass.t.sol\":911,\"test/OGPassBase.sol\":85,\"test/OGPassFuzz.t.sol\":245,\"test/helpers/Merkle.sol\":61,\"test/mocks/MockOG.sol\":34},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1106,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/OGPass.sol:369: Reentrancy in OGPass._pullExact(address,uint256) (src/OGPass.sol#369-374):\n[low/medium] timestamp at src/OGPass.sol:363: OGPass._reservedFounderSlots() (src/OGPass.sol#363-366) uses timestamp for comparisons\n[low/medium] timestamp at src/OGPass.sol:217: OGPass.burnUnclaimedFounderReserve() (src/OGPass.sol#217-227) uses timestamp for comparisons\n[low/medium] timestamp at src/OGPass.sol:236: OGPass.buy(uint256,uint256) (src/OGPass.sol#236-252) uses timestamp for comparisons\n[low/medium] timestamp at src/OGPass.sol:258: OGPass.sell(uint256,uint256,uint256) (src/OGPass.sol#258-275) uses timestamp for comparisons\n[low/medium] timestamp at src/OGPass.sol:171: OGPass.claimFounder(bytes32[]) (src/OGPass.sol#171-187) uses timestamp for comparisons","passed":true},{"durationMs":333,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/OGPass.sol:208: ETH transferred without address checks (2 places)\n[low] centralization-risk at src/OGPass.sol:30: Centralization Risk (3 places)\n[low] large-numeric-literal at src/OGPass.sol:56: Large Numeric Literal\n[low] unsafe-oz-erc721-mint at src/OGPass.sol:184: Unsafe `ERC721::_mint()` (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5de0d2080e0fdb68c9f56795be111c4fcfa91654a487df2e70a4bd139ac3fb5d","verifiedTreeHash":"998b5a699ea2eab70a9a64b19311c60f455bc0ea","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":2276,"exitCode":0,"name":"build","output":"Compiling 49 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.14s\nCompiler run successful!\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:184:9\n    │\n184 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:172:13\n    │\n172 │         if (block.timestamp >= claimDeadline) revert ClaimClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OGPass.sol:186:9\n    │\n186 │         emit Claim(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:218:13\n    │\n218 │         if (block.timestamp < claimDeadline) revert ClaimStillOpen();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-oz-erc721-mint]: `ERC721._mint` does not check that the recipient can receive the token; use `_safeMint`\n    ╭▸ src/OGPass.sol:248:9\n    │\n248 │         _mint(msg.sender, tokenId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-oz-erc721-mint\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:237:13\n    │\n237 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:259:13\n    │\n259 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGPass.sol:364:13\n    │\n364 │         if (block.timestamp >= claimDeadline) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":2707,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/DeployOGPass.t.sol:DeployOGPassTest\n[PASS] test_deployOnEmptyChain() (gas: 2260730)\n[PASS] test_mainnetConfigMatchesBrief() (gas: 10361)\n[PASS] test_runtimeFitsEip170() (gas: 2176284)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.06ms (486.75µs CPU time)\n\nRan 8 tests for test/OGPassFuzz.t.sol:OGPassFuzzTest\n[PASS] testFuzz_anySellOrderEmptiesCurve(uint8,uint256) (runs: 256, μ: 4565989, ~: 2976783)\nLogs:\n  Bound result 1\n\n[PASS] testFuzz_burnUnclaimedMath(uint8,uint8,uint256) (runs: 256, μ: 3452032, ~: 2160007)\nLogs:\n  Bound result 0\n  Bound result 0\n  Bound result 8039\n\n[PASS] testFuzz_buyRespectsMaxPrice(uint256) (runs: 256, μ: 222018, ~: 170638)\n[PASS] testFuzz_claimWindow(uint256) (runs: 256, μ: 274724, ~: 206859)\nLogs:\n  Bound result 10656672\n\n[PASS] testFuzz_deadlines(uint256) (runs: 256, μ: 287453, ~: 388964)\n[PASS] testFuzz_randomSequencePreservesInvariants(uint256,uint8) (runs: 256, μ: 7098972, ~: 2929080)\nLogs:\n  Bound result 58\n\n[PASS] testFuzz_sellRespectsMinOut(uint256) (runs: 256, μ: 375940, ~: 399745)\n[PASS] testFuzz_strangerCannotClaimWithAnyFounderProof(address,uint8) (runs: 256, μ: 199369, ~: 199431)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 403.05ms (1.25s CPU time)\n\nRan 67 tests for test/OGPass.t.sol:OGPassTest\n[PASS] test_burnUnclaimed_math() (gas: 1335854)\n[PASS] test_burnUnclaimed_noFoundersBurnsEverything() (gas: 293613)\n[PASS] test_burnUnclaimed_nothingToBurnWhenEmpty() (gas: 44571)\n[PASS] test_burnUnclaimed_nothingToBurnWhenExactlyCovered() (gas: 576086)\n[PASS] test_burnUnclaimed_nothingToBurnWhenUnderfunded() (gas: 772433)\n[PASS] test_burnUnclaimed_repeatableAfterNewFunding() (gas: 1178748)\n[PASS] test_burnUnclaimed_revertsBeforeDeadline() (gas: 246910)\n[PASS] test_buy_deadlineInclusive() (gas: 299626)\n[PASS] test_buy_firstPass() (gas: 388939)\n[PASS] test_buy_pricesFollowCurve() (gas: 5069084)\n[PASS] test_buy_pullsExactlyPriceEvenWithHigherMaxAndAllowance() (gas: 319676)\n[PASS] test_buy_rejectsFeeOnTransferToken() (gas: 920598)\n[PASS] test_buy_revertsAboveMaxPrice() (gas: 170941)\n[PASS] test_buy_revertsPastDeadline() (gas: 158947)\n[PASS] test_buy_revertsWhenPaused() (gas: 210836)\n[PASS] test_buy_revertsWithoutAllowance() (gas: 233248)\n[PASS] test_buy_slippageAfterFrontRun() (gas: 450352)\n[PASS] test_cap_publicStopsAtNineFiftyOneWhileFoundersReserved() (gas: 229073878)\n[PASS] test_cap_unclaimedFounderSlotsOpenAfterDeadline() (gas: 228718519)\n[PASS] test_claimFounder_allFortyNine() (gas: 9855258)\n[PASS] test_claimFounder_capFromFounderCount() (gas: 372994)\n[PASS] test_claimFounder_deadlineBoundary() (gas: 723074)\n[PASS] test_claimFounder_double() (gas: 413670)\n[PASS] test_claimFounder_doubleAfterBuyback() (gas: 693592)\n[PASS] test_claimFounder_invalidProof_empty() (gas: 46929)\n[PASS] test_claimFounder_invalidProof_nonFounder() (gas: 196809)\n[PASS] test_claimFounder_invalidProof_wrongLeaf() (gas: 198696)\n[PASS] test_claimFounder_memberCannotClaim() (gas: 492842)\n[PASS] test_claimFounder_notPausable() (gas: 382118)\n[PASS] test_claimFounder_valid() (gas: 439470)\n[PASS] test_constructorDoesNotTouchTokenCode() (gas: 13181)\n[PASS] test_constructorRejectsFounderCountAboveCap() (gas: 10494)\n[PASS] test_constructorRejectsZeroOwner() (gas: 8616)\n[PASS] test_constructorRejectsZeroToken() (gas: 8344)\n[PASS] test_fundFounderReserve() (gas: 242384)\n[PASS] test_fundFounderReserve_rejectsFeeOnTransfer() (gas: 823610)\n[PASS] test_fundFounderReserve_zeroReverts() (gas: 34851)\n[PASS] test_metadataAndImmutables() (gas: 156033)\n[PASS] test_onePerWallet_buyTwice() (gas: 450783)\n[PASS] test_onePerWallet_founderCanBuyAfterBuyback() (gas: 898027)\n[PASS] test_onePerWallet_founderCannotBuy() (gas: 486383)\n[PASS] test_ownerCannotExtractReserves() (gas: 656816)\n[PASS] test_ownershipHandoverIsTwoStep() (gas: 149759)\n[PASS] test_pause_onlyOwnerAndOnlyBuys() (gas: 1838254)\n[PASS] test_sellFounder_doesNotTouchCurveReserve() (gas: 902462)\n[PASS] test_sellFounder_paysExactlyBuyback() (gas: 700306)\n[PASS] test_sellFounder_reserveNotSharedWithCurve() (gas: 667831)\n[PASS] test_sellFounder_revertsForPublicPass() (gas: 501533)\n[PASS] test_sellFounder_revertsNotOwner() (gas: 568683)\n[PASS] test_sellFounder_revertsWithoutReserve() (gas: 582127)\n[PASS] test_sellFounder_worksAfterDeadlineAndWhenPaused() (gas: 685057)\n[PASS] test_sell_everyOrderEmptiesReserve() (gas: 2473379)\n[PASS] test_sell_notPausable() (gas: 455050)\n[PASS] test_sell_payoutUsesCurrentSupplyNotPurchasePrice() (gas: 645644)\n[PASS] test_sell_reentrancyGuardHolds() (gas: 1119997)\n[PASS] test_sell_revertsBelowMinOut() (gas: 340107)\n[PASS] test_sell_revertsForFounderPass() (gas: 672787)\n[PASS] test_sell_revertsNotOwner() (gas: 368253)\n[PASS] test_sell_revertsPastDeadline() (gas: 332307)\n[PASS] test_sell_singlePass() (gas: 497280)\n[PASS] test_sell_slippageAfterSomeoneElseSells() (gas: 695897)\n[PASS] test_sell_thenBuyAgainSameWallet() (gas: 670298)\n[PASS] test_setBaseURI() (gas: 412972)\n[PASS] test_soulbound_founderPass() (gas: 486389)\n[PASS] test_soulbound_publicPass() (gas: 457776)\n[PASS] test_supportsInterface() (gas: 25617)\n[PASS] test_views() (gas: 653885)\nSuite result: ok. 67 passed; 0 failed; 0 skipped; finished in 403.13ms (798.56ms CPU time)\n\nRan 1 test for test/OGPass.invariant.t.sol:OGPassInvariantTest\n[PASS]\nOGPassInvariantTest invariants:\n[PASS] invariant_conservation\n[PASS] invariant_curveReserveEqualsSumOfOutstandingPrices\n[PASS] invariant_founderReserveNeverExceedsBalance\n[PASS] invariant_onePassPerWallet\n[PASS] invariant_solvent\n[PASS] invariant_supplyCapAndFounderCounts\n OGPassInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| OGPassHandler | burnUnclaimed | 475   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | buy           | 566   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | claim         | 493   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | fundReserve   | 495   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sell          | 517   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | sellFounder   | 531   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | togglePause   | 481   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| OGPassHandler | warp          | 538   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 224\n  Bound result 52575914\n  Bound result 1760123456\n  Bound result 70727563945484835712008\n  Bound result 18446744073709551614\n  Bound result 685\n  Bound result 323982387882310988128144\n  Bound result 8366\n  Bound result 10\n  Bound result 6081\n  Bound result 99\n  Bound result 24\n  Bound result 1292\n  Bound result 345566316429771773804871\n  Bound result 7\n  Bound result 584\n  Bound result 950\n  Bound result 377\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.62s (2.61s CPU time)\n\nRan 4 test suites in 2.62s (3.43s CPU time): 79 tests passed, 0 failed, 0 skipped (79 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"OGPass.acceptOwnership()\",\"OGPass.burnUnclaimedFounderReserve()\",\"OGPass.buy(uint256,uint256)\",\"OGPass.claimFounder(bytes32[])\",\"OGPass.fundFounderReserve(uint256)\",\"OGPass.renounceOwnership()\",\"OGPass.safeTransferFrom(address,address,uint256)\",\"OGPass.safeTransferFrom(address,address,uint256,bytes)\",\"OGPass.sell(uint256,uint256,uint256)\",\"OGPass.sellFounder(uint256)\",\"OGPass.setBaseURI(string)\",\"OGPass.setBuysPaused(bool)\",\"OGPass.transferFrom(address,address,uint256)\",\"OGPass.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":178,\"foundry.toml\":23,\"launch.json\":15,\"remappings.txt\":2,\"script/DeployOGPass.s.sol\":43,\"src/OGPass.sol\":375,\"test/DeployOGPass.t.sol\":49,\"test/OGPass.invariant.t.sol\":202,\"test/OGPass.t.sol\":911,\"test/OGPassBase.sol\":85,\"test/OGPassFuzz.t.sol\":245,\"test/helpers/Merkle.sol\":61,\"test/mocks/MockOG.sol\":34},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a368356ceb1ac2d76af163b26844bdacd666cf5e5577f57cb33833c9d5c04f90","verifiedTreeHash":"ecb3e65e3a0360a84e7b2b8f2d90469a71640681","verifierVersion":"0.1.0+fb8278c5"}]}