{"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":"ea109756-4be9-42b7-8df9-a3457e934599","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"11224a2afbf0a0b8e7087439160b0bc60c92784a52e3a1169180295347d9cfaf","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"a2cea02e8b33e91eeb784e0e5eaeb571cd62cd69e768d335d5834e24b3aa46e5","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"a2cea02e8b33e91eeb784e0e5eaeb571cd62cd69e768d335d5834e24b3aa46e5","skillId":"adapt-contract-project","state":"failed"},{"acceptedSubmissionHash":"f6fba6ca032ee84f003a68905c94bae120958343263458e253c8a5b21a24778f","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"db105aedc3878e950f41a5056efef9393653fd0cd343b172be497fa6066025ea","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"7c27ea953fb440c9fc302a3150c9259c908ae5d1fcdbcc4a977014d5a12acc5a","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"be3876f3914ec424cc9fb7bda9d7c775082790137a2734e37aa2a207ea862347","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":"6d8779d1da8c0a9b1c5d4510f2e7788ae8f29cf74530ff1ec5f7c0f61eb7c305","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"9dd776b32ec3b1ec446d7e47dca6d2b4f43ce8ff3a036fe8dcd239ac0d901d95","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"e062878ea43a049ae5cb4833ea92052dec464781a9843b410a93df589cd83a91","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"waiting"},{"acceptedSubmissionHash":"7bb0b60c9bace7a5a316536a27d797cb9c7a383a970e34701b94213834db5229","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"waiting"}],"objective":"Deploy the Worker Frens collection (wFREN, 2222 on-chain pixel frens) on Ethereum, exactly as it is in the repository: the COLLECTION launch, two contracts from src/FrensPlacement.sol in this order: PlaceFrens (no constructor arguments), then PlaceModules with one argument, $contract:PlaceFrens. PlaceFrens creates the price table (FrenPrices) and the collection (IMD6900Frens, named Worker Frens); PlaceModules creates its swapper, ETH minter and workers' and WL gate. They create them through the standard CREATE2 deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C with the creation code in src/FrensCode.sol and the salts in src/FrensPlan.sol, so the collection lands at 0x69007Ce82E0BF7981780585afF7c597415903547, the swapper at 0x6900453deFAc8Bb12eabdcf57CCC5a14E7628AeE, the minter at 0xBbb2796c9C54330788915990Ba36FDDe6dC198cF and the gate at 0x3F8d1553Cb71C8B5af013Ce985591d9B9BCD9ce2 whoever deploys. Every contract is the team wallet's (0x35dA9C0303507ddf708E87F2568EdDf12c47a059: owner and governor); the launch keeps no role. Without the CREATE2 deployer (a fresh chain) it creates the same contracts with its own CREATE2 and calls nothing else that existed before it. About 12.3M gas in one transaction, all in. The art (WorkerArt1, WorkerArt2, WorkerFrensRenderer) is a separate launch; after both, the team wallet runs script/frens/DeployFrens.s.sol setup(). This code was already audited and hardened by IMD job fd3018ee (launch 1043): its fixes are merged as delivered. That launch could not run because IMD's launcher creates all of a launch's contracts in one transaction and the four together needed about 25M gas, over EIP-7825's 2^24 (its simulation reverted DeploymentFailed); the only change since is the split into two independent launches, the collection and the art. Do not change src/frens/, src/art/, src/FrensCode.sol, src/FrensPlan.sol, the salts or foundry.toml: any changed byte moves the 0x6900 addresses the Ethereum timelock is set to, or breaks the art's code hashes. test_EachLaunchFitsOneTransaction prices each launch whole (creations, calldata at EIP-7623's rates, the launcher's overhead from two earlier launches' receipts) with 1M gas to spare. Everything builds offline (lib/ is vendored). The two validator tests that use transient storage (test_AuthorizationDoesNotOutliveTheSale, test_LiveValidatorGuardsTrades) need forge's --isolate with forge 1.4; they pass as is with 1.8.","parentJobId":null,"planHash":"83fb83650091b4bd5fb978b926bd5beee2874adb3982a17676fdbbeabbbaaba4","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"ea109756-4be9-42b7-8df9-a3457e934599","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1066-placefrens-placemodules"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51475","feedbackHash":"dcd46aaf3e485106e62002af90bec7d1726c0408758cf85070fb3a70e4c0f4ff","nodeKey":"adapt_contract_project","submissionHash":"11224a2afbf0a0b8e7087439160b0bc60c92784a52e3a1169180295347d9cfaf","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51880","feedbackHash":"77d6af6b52ce53d61434af7423ab998f7e38ee068421699191da3d2efdefe059","nodeKey":"audit_economics","submissionHash":"f6fba6ca032ee84f003a68905c94bae120958343263458e253c8a5b21a24778f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51881","feedbackHash":"c50f683e468f4042dab7ce91cfafb22f7194100f175effb3fe0f0f8551933fb5","nodeKey":"audit_flow","submissionHash":"db105aedc3878e950f41a5056efef9393653fd0cd343b172be497fa6066025ea","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51331","feedbackHash":"1498af4df34adb32ea683467d5147bf986c10008652723985cb938aa3ff067fa","nodeKey":"audit_imported_code","submissionHash":"7c27ea953fb440c9fc302a3150c9259c908ae5d1fcdbcc4a977014d5a12acc5a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52397","feedbackHash":"4289c7d7944554457f8d00719c34f12bc6058b512f74e6dbf08e08a810730bb1","nodeKey":"audit_judge","submissionHash":"be3876f3914ec424cc9fb7bda9d7c775082790137a2734e37aa2a207ea862347","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50903","feedbackHash":"9f0ea372a11c7376445e9480f0e742e21b14772249b0145f7f3eff5ed7a07923","nodeKey":"audit_math","submissionHash":"6d8779d1da8c0a9b1c5d4510f2e7788ae8f29cf74530ff1ec5f7c0f61eb7c305","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51878","feedbackHash":"eec54fdb41c33f616f37be1d8a81e1685658f7eb6e9c8d20f9cb74bd6d1d1dcc","nodeKey":"audit_permissions","submissionHash":"9dd776b32ec3b1ec446d7e47dca6d2b4f43ce8ff3a036fe8dcd239ac0d901d95","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51136","feedbackHash":"cf54024313e1e390510923e374928e48a24c0481b52c7cdceb05afe993e35ac0","nodeKey":"manifest","submissionHash":"e062878ea43a049ae5cb4833ea92052dec464781a9843b410a93df589cd83a91","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51283","feedbackHash":"b661c0f1f8efcb89ea984df723ff50f4613e385b6f65c8b0d021a3ee48a88125","nodeKey":"write_foundry_tests","submissionHash":"7bb0b60c9bace7a5a316536a27d797cb9c7a383a970e34701b94213834db5229","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"408bb9df0a128a15e57dbc578e35694ef276d5d92c1b925c98aa5dc8cc9ed452","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"9d470ec2fd1aa2ac61b75d874d201f0e3b69aa95d9b567d8f38a81d02f5a8e08","device":"4d218e2d11a36af4","findings":[],"hash":"11224a2afbf0a0b8e7087439160b0bc60c92784a52e3a1169180295347d9cfaf","nodeId":"460d43ac-eb69-4825-a084-865a6ca38dad","outcome":"completed","summary":"Updated the two-contract manifest, hardened setup scripts, and added regression tests. Pinned code and addresses remain unchanged.\n\nValidation passed: build, 189 tests, and three targeted mainnet-fork tests. Collection launch: **12.31M gas**.\n\nThe reproduced spent-Permit2-nonce defect remains unresolved because fixing it would change prohibited bytecode. Changes, operational mitigations, and remaining risks are documented in [ADAPTATION.md](/home/ubuntu/.identitymd/work/ea109756-4be9-42b7-8df9-a3457e934599/460d43ac-eb69-4825-a084-865a6ca38dad/ADAPTATION.md).","treeHash":"4733a195f015d26fd2ab4945673f28f89592c97b","usage":{"cachedInputTokens":3089536,"inputTokens":146862,"model":"gpt-6-astra","outputTokens":26460,"runtime":"codex","turns":9,"wallClockMs":1112376}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"523ef565dd740e25","findings":[{"citation":"resolved","description":"Two definitions of the floor coexist. quote() (line 504-507) values the floor as _unswept() extra + floorImd + reserve/floorRate, so a mint always pays for the $IMD that arrived since the last sweep (IMD's 0.25 job refunds, anyone's transfer, the documented 'sweep path' for $IMD receipts). floorPerFren() (line 811) is reserve/out and floorImd/out only: recycle() (line 896) pays that, and buyTreasury() (line 914-915) charges twice that. The unswept $IMD only becomes floorImd inside _buyFloor (line 833), which runs at the end of every mint and in buyFloor(); but buyFloor() reverts Cap() whenever the buy is skipped (line 819, 835: maxImdPerBuy == 0, no swapper, or same block), undoing its own sweep, so while floor buys are paused (setParams(_,0,0): the state DeployFrens.setup() leaves until the timelock batch lands, and the state the README prescribes for recovery) nothing but a mint sweeps. Seam (boundary x invariant, in the Numerical Gap guide's terms): the invariant 'a mint never costs less than the floor it joins / buying from the treasury costs twice the floor / recycle pays the floor' is kept only against the swept floor, while the mint price is computed against the unswept one. Consequences: (a) a holder who recycles while $IMD is unswept forfeits extra/out of it to the remaining holders (with buys paused they cannot sweep first); (b) a treasury fren sells for 2*(booked floor) although the quote says a fren is worth (booked + extra)/out, so whoever buys it before the next sweep captures a share of the unswept $IMD the other holders are owed: buy at 2F, sweep (any mint, or buyFloor if buys are on), recycle at (V + 2F + extra)/(out+1); net gain (extra - V)/(out+1), i.e. profitable whenever the unswept $IMD exceeds the booked floor value V, which is the normal state right after the first frens (V is 0.19 $IMD a fren before fees) if the team or anyone funds the floor by a plain transfer while buys are paused. In the proof: 4 frens out, 100 $IMD unswept, buys paused: quote(1) = 25.44 $IMD, recycle pays 0.22 $IMD of the IMD part, a treasury fren costs 0.88 $IMD in both parts and is worth 25.45 $IMD after the next mint's sweep; with buys on the round trip buyTreasury -> buyFloor -> recycle returns 25.55 for 0.88. Minimal fix preserving the design: count _unswept() extra in floorPerFren()'s IMD part and book it (floorImd += extra) at the top of recycle() and buyTreasury(), or let the sweep in _buyFloor persist when the buy is skipped (return false after the sweep instead of reverting in buyFloor). Any change to src/frens moves FRENS_AT and SWAPPER_AT (the timelock batch's targets), so this is the requester's scope decision; the operational alternative is never to send $IMD to the collection while buys are paused and to sweep (buyFloor) before any treasury buy.","line":811,"path":"src/frens/IMD6900Frens.sol","reproduction":"State: traits sealed, mint open, swapper set, 4 frens out (alice 1-2, bob 3-4), governor calls setParams(1, 0, 0) (buys paused), then 100e18 $IMD is transferred to the collection (imd.mint/transfer to address(frens)). Calls: (1) frens.quote(1) -> 25.44e18 (counts the 100 $IMD: value = extra + floorImd + reserve*1e18/floorRate, /4). (2) frens.buyFloor(0) -> reverts Cap() (nothing bought, the sweep undone). (3) alice: frens.recycle(1) -> imdPaid = floorImd/4 = 0.2205e18, no share of the 100 $IMD (expected: a share of the $IMD waiting to be bought in, as quote prices it). (4) floorPerFren() -> (f6900, fImd); bob: frens.buyTreasury(1, 2*f6900, 2*fImd) succeeds for 0.88e18 $IMD-equivalent (expected: twice the floor quote() enforces, ~50.9e18). (5) alice: frens.requestMint(1, max) sweeps: floorImd >= 100e18; fren 1's floor share is now 25.45e18 $IMD-equivalent, 29x what bob paid. Variant with buys on (setParams default): bob buyTreasury(1,...) at 0.88e18, buyFloor(0), recycle(1) -> 25.55e18 back in one sequence. Run: forge test --match-path test/scratch/UnsweptFloor.t.sol -vv (both tests fail on the current code; they assert the intended no-profit property).","severity":"medium","snippet":"        (imd6900Part, imdPart) = (reserve / out, floorImd / out);","title":"floorPerFren() leaves unswept $IMD out of the floor that quote() charges for: recycle underpays and buyTreasury undercharges by its share until the next sweep"}],"hash":"6d8779d1da8c0a9b1c5d4510f2e7788ae8f29cf74530ff1ec5f7c0f61eb7c305","nodeId":"f602914d-04b0-4dcc-a4b3-583e47615f7d","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds one substantiated finding and 60 coverage rows (all 56 listed entry points plus four invariants), none unreached. No files outside the findings file and `test/scratch/` were touched.\n\n**What I audited (Math Precision, Boundary, Numerical Gap)**\n\n- Decoded the pinned price table: 6667 bytes, monotone from 0.6901 to 3.2378 IMD, total 5422.35 IMD, and every `quote(count)` read stays inside it.\n- Traced the floor arithmetic end to end: `quote` versus `recycle` round trips in IMD6900 terms under all three rate orderings (average below spot, above spot, realized below both). The minter always loses at least the job share, so the \"never below the floor\" rule holds.\n- Checked the swapper's limit math against live mainnet state. The pair hook's `fee()` is 690 bips with no setter, so the half-fee limit is 345 bips, the `uint160` cast has headroom until an absurd price, and the `limit == spot` revert at fee under 2 bips is unreachable. The rate formula loses under 1 wei at today's sqrt price.\n- Verified the slow average's weighting, the gate's 420 boundary, the `uint40`/`uint32`/`uint8` casts, the renderer's table indexing (35 bytes, highest index 34, matching the collection's trait bounds), and the launch gas arithmetic.\n\n**The finding (medium, with a proof that fails on the current code)**\n\n`floorPerFren()` at `src/frens/IMD6900Frens.sol:811` counts only booked `floorImd` and `reserve`, while `quote()` also counts unswept IMD. `recycle` and `buyTreasury` therefore price the floor lower than mints are charged, and with buys paused the only sweep is a mint. Concrete numbers from the proof:\n\n| state | value |\n|---|---|\n| quote(1) with 100 IMD unswept, 4 out | 25.44 IMD |\n| recycle pays (IMD part) | 0.22 IMD |\n| treasury fren price, both parts | 0.88 IMD |\n| that fren's share after the next sweep | 25.45 IMD |\n\nThe capture is profitable whenever unswept IMD exceeds the booked floor, which is plausible right after the first frens if anyone funds the floor by transfer. The proof is in `test/scratch/UnsweptFloor.t.sol`. A fix touches `src/frens`, which moves the 0x6900 addresses, so I noted the operational alternative as well.\n\n**Not reached**: nothing in the assigned area. I did not re-audit the art chunks' pixel data or the x402 service beyond the digest arithmetic.","treeHash":null,"usage":{"cachedInputTokens":5378773,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":87661,"runtime":"claude","turns":52,"wallClockMs":1835240}},{"artifacts":[],"attempt":1,"bundleHash":"3a4a2024a51229a479231e8ec8ca66669c3fe832015470940b4fc4852716daf0","device":"b5ff2623f878600d","findings":[],"hash":"7bb0b60c9bace7a5a316536a27d797cb9c7a383a970e34701b94213834db5229","nodeId":"be5e071d-2dde-4a72-bbaa-f9c9fe25d710","outcome":"completed","summary":"Added offline payment and failure-path tests, plus stateful invariants covering funds, mint credits, and supply. Changes are confined to `test/`.\n\nVerification passed:\n- `forge build --offline`\n- `forge test --offline`: 211 passed, 5 existing fork suites skipped.\n- New invariants: 20,480 calls, no unexpected reverts.","treeHash":"9afae8eeb94653e7d43fef931ac527cef2a2a850","usage":{"cachedInputTokens":4119296,"inputTokens":132111,"model":"gpt-6-astra","outputTokens":20819,"runtime":"codex","turns":7,"wallClockMs":779903}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d2d5a117dd72f6b4","findings":[{"citation":"resolved","description":"The repository's manifest was not updated by the split commit (e1bdafd). It lists PlaceFrens, WorkerArt1, WorkerArt2 and PlaceModules with three constructor words, while src/FrensPlacement.sol's PlaceModules now takes one (PlaceFrens placed) and the art is a separate launch. Its notes also still quote the first plan's addresses (collection 0x69006841..., swapper 0x6900d1D4...) and a WorkerArtIndex hash check PlaceModules no longer performs, whereas the Ethereum timelock batch already queued on mainnet (operation 0x6ebf60b5..., executable at timestamp 1791649787) targets the current plan's 0x69007Ce8... and 0x6900453d.... A deployer that takes this file as it stands re-creates the failure of launch 1043: the three extra words are ignored by PlaceModules' constructor (it deploys and wires the frens correctly), but the two art chunks are created in the same transaction and the whole thing prices above the cap. The adapter must rewrite launch.json to exactly two entries: PlaceFrens with [] and PlaceModules with [\"$contract:PlaceFrens\"], and refresh the notes' addresses.","line":17,"path":"launch.json","reproduction":"test/scratch/ManifestGas.t.sol (raw CREATE of each init code, priced as test_EachLaunchFitsOneTransaction prices a launch: 21,000 + EIP-7623 calldata + creations + 300,000 + 7 gas a byte): PlaceFrens 7,606,016; WorkerArt1 5,199,697; WorkerArt2 3,993,342; PlaceModules over the manifest's three words 3,990,376; creations 20,789,431; all in 23,695,404 > 16,777,216. The same pricing of the split collection launch (PlaceFrens, then PlaceModules with one word) is 13,334,236, under the cap with more than 1M to spare. PlaceModules deployed from abi.encode(pf, art1, art2) reports frens() == pf.frens() == 0x69007Ce82E0BF7981780585afF7c597415903547, so the mismatch does not fail loudly; only the gas does.","severity":"medium","snippet":"      \"contract\": \"PlaceModules\",\n      \"constructorArgs\": [\n        \"$contract:PlaceFrens\",\n        \"$contract:WorkerArt1\",\n        \"$contract:WorkerArt2\"\n      ]","title":"launch.json still describes the pre-split four-contract launch (three-argument PlaceModules, old addresses): the launch it describes needs about 23.7M gas, over EIP-7825's 2^24"},{"citation":"resolved","description":"The floor's 'a mint never costs less than the floor it joins' guarantee and the LastFrenOut fix both assume the floor always has an owner. Before the first mint it has none, and nothing stops value from arriving: receive() takes ETH (the launch hook's fee slice once the queued batch points it here, royalties, anyone), buyFloorWithEth is callable by anyone once setup()/resume() set maxEthPerBuy, _buyFloor sweeps $IMD sent to the contract into floorImd, and quote() counts unswept $IMD only inside the `out != 0` branch. With totalMinted == 0 quote() returns the curve's price, so whoever mints first (a WL wallet or the public once the governor calls setMintOpen(true), or any governor mint) buys a claim on everything in the floor for 0.69 $IMD a fren and can sell one of two straight back for its half. The README's post-launch order (setup, WL, timelock batch + resume(), then open) reaches this state whenever opening precedes the first mint; the ADAPTATION's own arithmetic shows that charging the first minter the floor's value does not close it either (a fren bought for V is then worth 2V-0.5), so the sound fix is the one LastFrenOut used: the state must not arise. Options: have _buyFloor/buyFloorWithEth refuse (or wait) while totalMinted == 0 and make quote() treat unswept $IMD as the first request's price, or mint the curve's first frens to IMD6900 (DeployFrens.firstFrens) before the hook's fee address moves and before resume()/setMintOpen(true), and document that order as mandatory. Measured magnitude today: the hook's current fee address (the first collection) holds 0.0000169 ETH, so the fee slice alone is small for now; the gap is in the contract, not in today's balances.","line":502,"path":"src/frens/IMD6900Frens.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"solady/tokens/ERC20.sol\";\nimport {IMD6900Frens, IFrenSwapper} from \"src/frens/IMD6900Frens.sol\";\n\n/// @dev A plain ERC20 with explicit allowances only (no implicit Permit2 allowance, as $IMD on mainnet)\ncontract ProofToken is ERC20 {\n    string internal n;\n\n    constructor(string memory n_) {\n        n = n_;\n    }\n\n    function name() public view override returns (string memory) {\n        return n;\n    }\n\n    function symbol() public view override returns (string memory) {\n        return n;\n    }\n\n    function mint(address to, uint256 a) external {\n        _mint(to, a);\n    }\n\n    function _givePermit2InfiniteAllowance() internal pure override returns (bool) {\n        return false;\n    }\n}\n\n/// @dev A swapper that pays `rate` IMD6900 per $IMD (and 3000 $IMD per ETH), minted fresh: the live pools' shape\ncontract ProofSwapper is IFrenSwapper {\n    ProofToken public immutable out;\n    ProofToken public immutable imd;\n    uint256 public constant rate = 70_000;\n\n    constructor(ProofToken o, ProofToken i) {\n        out = o;\n        imd = i;\n    }\n\n    function floorRate() external pure returns (uint256) {\n        return rate * 1e18;\n    }\n\n    function imdToImd6900(uint256 imdIn, uint256, address to) external returns (uint256 got) {\n        imd.transferFrom(msg.sender, address(this), imdIn);\n        got = imdIn * rate;\n        out.mint(to, got);\n    }\n\n    function ethToImd6900(uint256, address to) external payable returns (uint256 got) {\n        got = msg.value * 3000 * rate;\n        out.mint(to, got);\n    }\n}\n\n/// @dev A price table where every fren costs 0.69 $IMD (6900 units of 0.0001), as the frens read it (a STOP, then\n///      3 bytes a fren)\ncontract ProofPrices {\n    constructor() {\n        bytes memory code = new bytes(1 + 3 * 2222);\n        for (uint256 i; i < 2222; ++i) {\n            (code[1 + 3 * i + 1], code[1 + 3 * i + 2]) = (bytes1(0x1a), bytes1(0xf4));\n        }\n        assembly (\"memory-safe\") {\n            return(add(code, 32), mload(code))\n        }\n    }\n}\n\n/// @notice The floor can hold value while no fren is out (fee ETH bought in by anyone, $IMD sent in), and quote()\n///         then prices the first mint at the curve alone (`out == 0` skips the floor), so the first minter takes the\n///         whole floor for the curve's price. Fails on the code as it is; passes once value cannot enter the floor\n///         before the first fren is out, or the first mint pays for the floor it takes.\ncontract Proof_FloorBeforeFirstMint is Test {\n    IMD6900Frens frens;\n    ProofToken imd;\n    ProofToken imd6900;\n    ProofToken idmd;\n    ProofSwapper swapper;\n    address governor = makeAddr(\"governor\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        imd = new ProofToken(\"IMD\");\n        imd6900 = new ProofToken(\"IMD6900\");\n        idmd = new ProofToken(\"IDMD\");\n        swapper = new ProofSwapper(imd6900, imd);\n        frens = new IMD6900Frens(\n            governor,\n            address(imd),\n            address(imd6900),\n            address(idmd),\n            makeAddr(\"permit2\"),\n            makeAddr(\"x402\"),\n            makeAddr(\"payTo\"),\n            makeAddr(\"keeper\"),\n            makeAddr(\"relayer\"),\n            address(new ProofPrices())\n        );\n        vm.startPrank(governor);\n        _rules(frens);\n        frens.sealTraits();\n        frens.setModules(address(swapper), address(0));\n        frens.setMintOpen(true);\n        vm.stopPrank();\n        imd.mint(alice, 10_000e18);\n        vm.prank(alice);\n        imd.approve(address(frens), type(uint256).max);\n    }\n\n    function test_FirstMinterTakesTheFloorThatWasThereBeforeAnyFren() public {\n        // fee ETH arrived (the launch hook's fee slice, royalties) while no fren is out, and anyone bought it in\n        vm.deal(address(this), 1 ether);\n        (bool ok,) = address(frens).call{value: 0.25 ether}(\"\");\n        assertTrue(ok);\n        (ok,) = address(frens).call(abi.encodeCall(frens.buyFloorWithEth, (0.25 ether, 0)));\n        // and $IMD arrived too (a refund, a gift): quote counts it once a fren is out\n        imd.mint(address(frens), 100e18);\n        assertEq(frens.totalMinted(), 0, \"no fren out yet\");\n        uint256 value = frens.floorImd() + frens.reserve() * 1e18 / swapper.floorRate() + 100e18;\n        emit log_named_decimal_uint(\"floor value before the first mint, in $IMD\", value, 18);\n\n        // the first mint: two frens at the curve's price\n        uint256 paid = frens.quote(2);\n        emit log_named_decimal_uint(\"the first request pays, in $IMD\", paid, 18);\n        vm.prank(alice);\n        (bool minted,) = address(frens).call(abi.encodeCall(frens.requestMint, (2, paid)));\n        if (!minted) return; // a first mint refused while the floor has no owner takes nothing\n        vm.prank(alice);\n        (uint256 got6900, uint256 gotImd) = frens.recycle(1);\n        uint256 back = gotImd + got6900 * 1e18 / swapper.floorRate();\n        emit log_named_decimal_uint(\"one of the two sold straight back, in $IMD\", back, 18);\n        assertLe(back, paid, \"selling one of the two straight back returns more than both cost\");\n    }\n\n    /// @dev The launch rules, as script/frens/DeployFrens.s.sol sets them\n    function _rules(IMD6900Frens f) internal {\n        (uint16[] memory c, uint8[] memory t) = _fill(3, 0, 0);\n        (c[0], c[1], c[2], t[1], t[2]) = (1598, 312, 312, 2, 2);\n        f.setTraitRules(0, c, t);\n        (c, t) = _fill(13, 2222, 0);\n        (c[12], t[12]) = (56, 3);\n        f.setTraitRules(1, c, t);\n        (c, t) = _fill(4, 2222, 0);\n        (c[3], t[3]) = (222, 1);\n        f.setTraitRules(2, c, t);\n        (c, t) = _fill(3, 2222, 0);\n        (c[2], t[2]) = (103, 1);\n        f.setTraitRules(3, c, t);\n        (c, t) = _fill(6, 2222, 0);\n        f.setTraitRules(4, c, t);\n        (c, t) = _fill(3, 266, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(5, c, t);\n        (c, t) = _fill(12, 2222, 0);\n        f.setTraitRules(6, c, t);\n        (c, t) = _fill(16, 140, 1);\n        (c[0], t[0]) = (2222, 0);\n        for (uint256 i; i < 6; ++i) {\n            t[[1, 3, 4, 10, 11, 14][i]] = 0;\n        }\n        (c[12], t[12], c[13], t[13]) = (56, 3, 56, 3);\n        f.setTraitRules(7, c, t);\n    }\n\n    function _fill(uint8 n, uint16 cap, uint8 tier) internal pure returns (uint16[] memory caps, uint8[] memory tiers) {\n        caps = new uint16[](n);\n        tiers = new uint8[](n);\n        for (uint8 i; i < n; ++i) {\n            (caps[i], tiers[i]) = (cap, tier);\n        }\n    }\n}","reproduction":"State: frens sealed, swapper set, mint open, totalMinted == 0. (1) 0.25 ETH arrives at the contract (fees), anyone calls buyFloorWithEth(0.25 ether, 0): reserve = 5.25e25 IMD6900 (750 $IMD at the swapper's 70,000 rate), out == 0. (2) 100 $IMD is sent to the contract: unswept, uncounted. (3) alice: quote(2) == 1.38e18 (the curve, 2 x 0.69); requestMint(2, 1.38e18) succeeds. (4) alice: recycle(1) pays 2.625e25 IMD6900 + 50.44 $IMD, i.e. 425.44 $IMD for a 1.38 $IMD outlay, and she still holds fren 2 worth the same. Expected: selling one of the two straight back never returns more than both cost. Proof: test/scratch/Proof_FloorBeforeFirstMint.t.sol (fails: 425440000000000000000 > 1380000000000000000).","severity":"medium","snippet":"        uint256 out = totalMinted - inTreasury();\n        if (out != 0) {","title":"Value can enter the floor while no fren is out, and quote() then prices the first mint at the curve alone, so the first minter takes the whole floor for 0.69 $IMD a fren"},{"citation":"resolved","description":"_soundSwapper() guards a pre-placed swapper against a seed taken in a pushed block, but the swapper PlaceModules creates in the launch block (the normal path, and the CREATE fallback) always reads rateAverage == spotRate of that same block, so the 2x band is tautological for it. If the IMD6900/$IMD pool's price is pushed in the launch's own block (the launch transaction is a public mempool transaction unless IMD's deployer uses a private relay; the brief does not say), rateAverage starts at the pushed value, floorRate() = min(spot, average) stays at it, and quote() values the reserve by it: with IMD6900 pushed 100x dearer for one block, every mint whose floor share exceeds the curve costs 100x the floor's real value until the average has walked back (1/64 of the gap a block, only on full 50 $IMD buys; mints of 0.19 $IMD move it 1/16,000 of the gap). ADAPTATION.md lists this as a trust assumption ('send the launch through a private relay and read rateAverage() against spotRate() after'); it is recorded here so the adapter confirms the deployer's relay policy or accepts it, and because the governor's recovery (setModules to a freshly deployed FrenSwapper seeded at a sane block) should be in the runbook. A pre-placed swapper seeded within the band (up to 2x) is adopted by design and has the same effect at up to 2x.","line":99,"path":"src/FrensPlacement.sol","reproduction":"test/scratch/LaunchBlock.t.sol test_LaunchBlockPushSeedsTheLaunchsSwapper: PoolManager stub at FrensPlan.POOL_MANAGER with sqrtPriceX96 = 2^96/26 (IMD6900 100x dearer) for the block PlaceModules(pf) runs in, 2^96/265 (about 70,225 IMD6900 per $IMD) before and after. Result: pm.swapper() == FrensPlan.SWAPPER_AT, rateAverage() == 676e18, spotRate() == 70,225e18, floorRate() * 50 < spotRate(). Wired by setup(), quote(1) with reserve R returns R * 1e18 / 676e18 instead of R * 1e18 / 70,225e18 whenever that exceeds the curve.","severity":"low","snippet":"        address s = _place(FrensPlan.SWAPPER_SALT, init);\n        if (!_soundSwapper(s)) {","title":"The launch's own swapper is seeded off the launch block's spot price with no bound: a same-block price push (a sandwich of a public launch transaction) skews floorRate 100x, and _soundSwapper passes b"},{"citation":"resolved","description":"PlaceModules' fallback (a plain CREATE when the pre-placed swapper's average is off the pool's price) is only effective if the team wires pm.swapper(). placed() falls back to FrensPlan.SWAPPER_AT when MODULES is unset or names an account with no code, so a run of setup() that omits MODULES wires exactly the skewed swapper the launch rejected, and the frens value their floor off it. Separately, the Ethereum timelock batch already scheduled on mainnet (operation 0x6ebf60b5..., setDistributor(0x69007Ce8...), setFeeExempt(0x6900453d...), updateFeeAddress(0x69007Ce8...)) exempts SWAPPER_AT; if the launch had to create its own swapper, the fee exemption lands on the attacker's contract and the floor's buys pay the pair hook's 690 bips until a second 48-hour batch. The script should refuse to wire SWAPPER_AT when it is not the swapper a PlaceModules reports (require MODULES on Ethereum), and the runbook should check PlaceModules.swapper() == FrensPlan.SWAPPER_AT before relying on the queued batch.","line":98,"path":"script/frens/DeployFrens.s.sol","reproduction":"test/scratch/LaunchBlock.t.sol test_SetupWithoutModulesWiresThePrePlacedSwapper: the exact FrenSwapper init code is placed at SWAPPER_AT through the CREATE2 deployer in a block whose stub price is 2^96/26, the price returns to 2^96/265, next block PlaceModules(pf) runs: pm.swapper() != SWAPPER_AT (its own). DeployFrens.placed() with no MODULES/SWAPPER env returns swapper == FrensPlan.SWAPPER_AT != pm.swapper(). The queued batch's targets were read from mainnet: the fork test test_fork_WorkersFirstThenPublic's scheduleBatch of batch(FRENS_AT, SWAPPER_AT, true, false) reverts with TimelockUnexpectedOperationState(0x6ebf60b5..., 1) because that operation already exists; getTimestamp(0x6ebf60b5...) == 1791649787.","severity":"low","snippet":"        swapper = vm.envOr(\"SWAPPER\", fromLaunch ? pm.swapper() : FrensPlan.SWAPPER_AT);","title":"setup() without MODULES (or with a wrong one) wires FrensPlan.SWAPPER_AT, which may be the pre-placed skewed swapper the launch refused; the already-queued timelock batch exempts SWAPPER_AT, not the l"},{"citation":"resolved","description":"The keeper chooses the Permit2 nonce and nothing checks `!_spent(nonce)` for the new approval. If a nonce that Permit2's nonceBitmap already has set is approved (a keeper whose nonce counter was reset or that retries with a used nonce), Permit2 refuses the payment, yet _reclaimLapsed() reads _spent(r.jobNonce) as 'the payment was taken' and returns false forever, so neither approveJob (BadJob: it needs r.jobs != 0, another 0.50 through retryJob), reveal() nor releaseLapsedJob() ever undo it. The allowance to Permit2 keeps the 0.50 inside the books (_unswept counts it), so it is never swept to the floor either: 0.50 $IMD stranded per occurrence, and the request needs a second payment. One line closes it: revert BadJob when _spent(nonce) at approval.","line":728,"path":"src/frens/IMD6900Frens.sol","reproduction":"test/scratch/SpentNonce.t.sol: alice requests one fren (jobs = 1, jobBudget 0.5e18). Permit2 mock marks nonce 42 spent for the frens. keeper: approveJob(id, 42, now + 600, q) succeeds; allowance(frens, permit2) == 0.5e18, jobBudget == 0. warp 601s. keeper: approveJob(id, 43, ...) reverts BadJob (r.jobs == 0 since the lapsed one is read as spent). releaseLapsedJob(id) reverts BadJob. balance == floorImd + jobBudget + 0.5e18 with extra == 0: the 0.50 is outside every path that could move it. Expected: approveJob(id, 42, ...) refuses a spent nonce (BadJob).","severity":"low","snippet":"        r.jobNonce = nonce;","title":"approveJob accepts a Permit2 nonce already marked spent: that 0.50 $IMD can neither be taken nor reclaimed and stays booked as an allowance for good"},{"citation":"resolved","description":"royaltyInfo names the collection itself as the royalty receiver for every sale, whatever the sale's currency. ETH reaches receive() and buyFloorWithEth; WETH is unwrapped by unwrapWeth(); $IMD is swept by _unswept(); IMD6900 sent directly is not added to reserve. Any other ERC-20 a marketplace pays the royalty in (OpenSea and Magic Eden settle in USDC, DAI and others when the listing is priced in them) has no function that can move it: there is no sweep, no owner withdraw, and no swap route, so that royalty is lost rather than bought into the floor. Either the owner needs a sweep for tokens outside the books (never IMD6900 above reserve, never $IMD inside the books) or the collection should be listed as ETH/WETH-only.","line":980,"path":"src/frens/IMD6900Frens.sol","reproduction":"Deploy any ERC-20 (not $IMD, IMD6900 or WETH), transfer 1,000e6 of it to the frens contract as a marketplace would pay a USDC royalty. Enumerate the contract's external functions: none takes a token address or moves arbitrary ERC-20 balances (buyFloor/_buyFloor move only $IMD via the swapper; recycle/buyTreasury move IMD6900 and $IMD by the books; unwrapWeth only the mainnet WETH constant). The balance is unreachable; `cast call <frens> 'balanceOf' on that token` stays 1,000e6 after every call sequence.","severity":"low","snippet":"    function royaltyInfo(uint256, uint256 salePrice) external view returns (address, uint256) {\n        return (address(this), (salePrice * royaltyBps) / 10_000);","title":"Royalties paid in any ERC-20 other than WETH (a USDC-denominated sale, for instance) are stranded in the collection: only ETH, WETH and $IMD have a path into the floor"},{"citation":"resolved","description":"Not a contract defect: live state. At block 26,148,914 the fork test reverts with the timelock's TimelockUnexpectedOperationState(0x6ebf60b5d6a49cec8df7fdb89ce96061ddb4316aaba0dc170007c223634c1ffa, 1) because the team has already scheduled this exact operation (setDistributor(0x69007Ce8..., true), setFeeExempt(0x6900453d..., true), updateFeeAddress(0x69007Ce8...)); getTimestamp() for it is 1791649787 (about 48 hours from this review) and isOperationDone is false. This confirms the task's statement that the timelock is bound to the current 0x6900 addresses (any byte change in src/frens, FrensCode or FrensPlan breaks it), and it means the whole-road fork suites will keep reporting one failure until the test schedules under a fresh salt or skips scheduling when the operation exists. The other 24 fork tests pass at this block, including test_LiveValidatorGuardsTrades (run alone, forge 1.8.3, no --isolate needed) that ADAPTATION.md recorded as failing.","line":679,"path":"test/FrensPlacement.t.sol","reproduction":"MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-test test_fork_WorkersFirstThenPublic -> [FAIL: custom error 0x5ead8eb5: 6ebf60b5...0001]. cast call 0xBd3ed9F4AbD9946cA6F59C8F13A3EbebDE1EA29D 'getTimestamp(bytes32)(uint256)' 0x6ebf60b5d6a49cec8df7fdb89ce96061ddb4316aaba0dc170007c223634c1ffa -> 1791649787. Expected by the test: an unscheduled operation.","severity":"info","snippet":"        tl.scheduleBatch(targets, values, datas, bytes32(0), salt, 48 hours);","title":"Fork test test_fork_WorkersFirstThenPublic now fails on mainnet: the batch it schedules (salt worker-frens-open-1, with moveHookFees) is already queued on the live timelock"},{"citation":"resolved","description":"The reference checklist asks that anything the launch forbids be listed so the adapter can see it. The brief states the code is to stay byte for byte and these were accepted by the prior audit, so they are recorded as trust assumptions on the team wallet 0x35dA9C0303507ddf708E87F2568EdDf12c47a059 (owner and governor until the handover): (1) configuration after deployment rather than in a constructor: setRenderer, setModules(swapper, gate), setTraitRules/addPairRule/sealTraits, setParams, setRoles, setTiers, setMaxMint, FrenWorkerGate.setWlRoot/openPublic, all run by DeployFrens.setup() and later calls; until then the collection cannot mint (TraitsNotSealed) and tokenURI reverts; (2) minting after launch is the product (requestMint/requestMintFor, 2222 cap, governor-only before setMintOpen(true)); (3) pausing: setMintOpen(false) closes the public mint at any time, setParams(_, 0, 0) pauses the floor's buys, setModules(0, _) makes quote() revert once reserve != 0 (every mint fails until a swapper is set again); (4) blocking holder transfers: setTransferValidator (owner or governor) can install a validator that rejects every holder-to-holder transfer, and the Limit Break default validator 0x721C008f... applies its operator list from deployment (the floor's recycle/buyTreasury never pass it); (5) no DELEGATECALL, CALLCODE, SELFDESTRUCT, proxy or initializer in any of the five placed contracts or the two launch contracts (test_PassesTheAdmissionScan, the protected probe's opcode scan). Also recorded: setGovernor is one step; a governor-chosen swapper can spend floorImd through buyFloor's approval (onlyFrens in FrenSwapper limits the shipped one); the x402 keeper approves one 0.50 payment per job.","line":1068,"path":"src/frens/IMD6900Frens.sol","reproduction":"Concrete calls, each from the team wallet after the launch: setMintOpen(false) -> a public requestMint reverts MintClosed (test_Audit_GovernorCanCloseMintAgain); setTransferValidator(<rejecting validator>) -> transferFrom(alice, bob, 1) reverts (test_Audit_OwnerOrGovernorCanBlockAllPeerTransfers); setModules(address(0), gate) with reserve != 0 -> quote(1) reverts calling floorRate() on address(0), so requestMint reverts; before setup(): requestMintFor(..) reverts TraitsNotSealed, tokenURI(1) reverts (test_Audit_ConstructorLeavesTheDocumentedTeamSetup).","severity":"info","snippet":"    function setMintOpen(bool open) external onlyGovernor {\n        mintOpen = open;","title":"Trust assumptions the brief keeps: post-deployment configuration by the team wallet and privileged switches on the collection (recorded, not defects)"},{"citation":"resolved","description":"Read in full, every external and public state-changing function traced for caller, value moved and trust: src/FrensPlacement.sol (Placer, PlaceFrens, PlaceModules), src/FrensPlan.sol, src/frens/IMD6900Frens.sol, src/frens/FrenSwapper.sol, src/frens/FrenMinter.sol, src/frens/FrenWorkerGate.sol, src/frens/FrenPrices.sol, script/frens/DeployFrens.s.sol, script/frens/FrensTimelockBatch.s.sol, launch.json, README.md, ADAPTATION.md, the supplied protected probe, and the tests test/FrensPlacement.t.sol, FrensLaunchReview.t.sol, FrensLaunchFailures.t.sol, frens/IMD6900Frens.t.sol, frens/FrenWorkerGate.t.sol, frens/IMD6900FrensValidator.t.sol. src/FrensCode.sol was taken as data (test_CodeIsWhatTheSourcesBuild and test_PlanFollowsFromTheCode pass: the bytes are the sources' own and the 0x6900 addresses follow). Verified in the vendored libraries: Solady ERC20's Permit2 allowance default (and that mainnet $IMD does not return an infinite Permit2 allowance, which would have overflowed _unswept's books), v4's Lock.IS_UNLOCKED_SLOT == keccak256('Unlocked') - 1 matches UNLOCKED_SLOT. Run: forge build --offline; forge test --offline (173 passed, 5 skipped); the fork suites against mainnet at blocks 26,148,89x-97x (24 passed; the two failures are the queued timelock operation above and an RPC rate limit, the latter passing on rerun); a fork check that recycle and buyTreasury move IMD6900 out of and back into the reserve once the collection is a distributor (passes: alice receives exactly 75,124 IMD6900 a fren, reserve == balance after each move). Not read: src/art/WorkerArt.sol, src/art/WorkerArtIndex.sol and src/frens/WorkerFrensRenderer.sol (the art launch, outside this launch; only their constructors and reference renders are exercised by the suites run), the vendored Uniswap v4 core and OpenZeppelin sources beyond the two constants above, and the source of the mainnet contracts the collection depends on ($IMD, IMD6900 and its transfer rule, the pair and POOL4 hooks, Limit Break's validator, the x402 proxy, Permit2): those were only probed by calls and by the fork tests. Not independently exercised: the live x402 settlement path beyond test/frens/IMD6900FrensPermit2.fork.t.sol, and the IMD swarm's relayer and keeper behaviour.","line":45,"path":"src/FrensPlacement.sol","reproduction":"forge test --offline -> 173 passed, 0 failed, 5 skipped. MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-contract Fork -> 24 passed, 2 failed (test_fork_WorkersFirstThenPublic: queued operation; IMD6900FrensValidator.fork: HTTP 429, passes alone).","severity":"info","snippet":"contract PlaceFrens is Placer {","title":"Coverage: what was read, what was run, and what could not be reached"}],"hash":"7c27ea953fb440c9fc302a3150c9259c908ae5d1fcdbcc4a977014d5a12acc5a","nodeId":"8edb51cd-b981-4d32-bb27-e9a90f5419b1","outcome":"completed","summary":"The review is complete. The findings file is at the repository root and every snippet, path and line was checked against the tree. No repository file was changed; only `.imd-findings.json` and my scratch tests under `test/scratch/` were written, and both are excluded from the submission by the harness's own rules.\n\n**Outcome.** No critical or high defect in the collection launch. The two contracts in `src/FrensPlacement.sol` place the price table, the collection, the swapper, the minter and the gate at the planned 0x6900 addresses whoever deploys, keep no role, make no forbidden calls on a fresh chain, and the collection launch alone prices at about 13.3M gas, under the EIP-7825 cap with room to spare. Nine entries were written: two medium, four low, three informational.\n\n**Medium findings**\n- **The manifest in the repository is stale.** `launch.json` still lists the pre-split four contracts with a three-argument `PlaceModules` and quotes the first plan's addresses in its notes. Deployed as it stands, the launch prices at about 23.7M gas, over the cap, which is exactly how launch 1043 failed. The adapter must rewrite it to two entries.\n- **The floor can hold value before any fren is out, and the first mint takes it at the curve price.** `quote()` skips the floor branch when no fren is out, while anyone can buy fee ETH into the reserve and send $IMD once buys are resumed. The proof test shows a first minter paying 1.38 $IMD for two frens and selling one back for 425 $IMD. The README's post-launch order reaches this state whenever opening precedes the first mint. The hook's current fee slice is tiny today, so the measured exposure is small, but the gap is in the contract.\n\n**Low findings** cover the launch block's spot price seeding the launch's own swapper with no bound (the 2x band is tautological for it, a private relay is the documented mitigation), the setup script wiring the planned swapper address when `MODULES` is omitted (and the already-queued timelock batch exempting that address rather than a fallback swapper), the keeper being able to approve an already-spent Permit2 nonce and strand 0.50 $IMD, and royalties paid in any ERC-20 other than WETH having no path into the floor.\n\n**Informational entries** record a live-state fact (the timelock batch for the current addresses is already scheduled on mainnet and executes in about 48 hours, which makes one fork test fail), the privileged switches and post-deployment configuration the brief keeps as trust assumptions, and the coverage statement.\n\n**Coverage.** Read in full: both launch contracts, the plan, the five collection contracts, both scripts, the manifest and docs, and the relevant tests. The creation-code data file was verified by the repository's own equality tests. Run: the offline suite (173 passed), the mainnet fork suites (24 passed, the two failures being the queued operation and an RPC rate limit that passed on rerun), and a fork check that `recycle` and `buyTreasury` move IMD6900 correctly once the collection is a distributor. Not read: the art launch's three contracts, the vendored Uniswap and OpenZeppelin sources beyond two verified constants, and the sources of the mainnet contracts the collection depends on, which were probed only by calls.","treeHash":null,"usage":{"cachedInputTokens":5843572,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":95614,"runtime":"claude","turns":67,"wallClockMs":2017228}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3f02755d8661ddf1","findings":[],"hash":"9471114a6a86f8e8d6103cb8623c22eaf2104dd8c04eb17558c0cb599e65c823","nodeId":"460d43ac-eb69-4825-a084-865a6ca38dad","outcome":"failed","summary":"needs input: Requester decision resolving the conflict between mandatory fixes dc3bd30d3476d639a35b6f4c64f95e29e1a8b69f9d015579268cde236c1913bd and b7d35d296a17ba4ebc4de9444d9a63a7bd72b2b38b360bd5d69137949c426cbf and the explicit prohibition on changing src/frens/, src/FrensCode.sol, src/FrensPlan.sol, salts or the required deployment addresses. Both supplied proofs were copied unchanged into test/scratch/ and reproduced with Forge 1.8.3 using forge test --offline --match-path 'test/scratch/Proof_*.t.sol' -vv: the pending-ETH mint/recycle returns 150.49082 IMD-equivalent for 0.6916 IMD paid; the unswept-IMD treasury round trip returns 25.70703125 for 1.13125 paid. Both proofs deploy IMD6900Frens directly, and the accounting defects are in its pinned implementation; script or placement changes cannot repair them. Changing that implementation also requires updating the pinned creation code and deterministic address plan, invalidating the current timelock targets. forge build --offline passed; forge t","treeHash":null,"usage":{"cachedInputTokens":1454080,"inputTokens":68557,"model":"gpt-6-astra","outputTokens":6266,"runtime":"codex","turns":9,"wallClockMs":582544}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2a6239e0c1b8c774","findings":[{"citation":"resolved","description":"Access-control lens, input validation on the privileged path. onlyKeeper (keeper or governor) is the only guard on approveJob; the function never checks `_spent(nonce)` for the nonce it is about to record. If the keeper passes a nonce whose Permit2 bit is already set (reuse after a restart, or a compromised keeper), the request is marked jobApproved with r.jobNonce = spent nonce, the Permit2 allowance is raised by JOB_PRICE and jobBudget is reduced by JOB_PRICE. Permit2 can never take that payment (nonce used), and every reclaim path (`_reclaimLapsed` in approveJob, reveal and releaseLapsedJob) returns false because `_spent(r.jobNonce)` is true, so the allowance is never lowered and `_unswept` excludes that 0.50 $IMD from the floor forever. The request can only be revealed after someone pays another 0.50 through retryJob. This is a keeper (trusted role) error, so impact is bounded: 0.50 $IMD stranded per occurrence and an extra 0.50 charged to whoever retries. It is listed here for completeness of the approveJob access review; the requester already records it as a known residual and the pinned bytecode cannot change without moving the 0x6900 addresses. Mitigation outside the contract: the keeper must check nonceBitmap(frens, nonce >> 8) before every approval (README already requires it).","line":724,"path":"src/frens/IMD6900Frens.sol","reproduction":"State: collection deployed and set up, request 1 minted (any minter). Steps: (1) Permit2 nonce 42 of the collection is already spent (e.g. an earlier job settled with it). (2) keeper calls approveJob(1, 42, block.timestamp + 600, q). Expected: revert BadJob (nonce unusable). Actual: succeeds; allowance(frens, PERMIT2) == 0.5e18, jobBudget == 0. (3) warp past the deadline; releaseLapsedJob(1) reverts BadJob and keeper approveJob(1, 43, ...) reverts BadJob (r.jobs == 0). (4) imd.balanceOf(frens) - floorImd - jobBudget == 0.5e18 permanently. Existing passing reproduction in the tree: test/FrensLaunchAdaptation.t.sol test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection (forge test --offline --match-test test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection).","severity":"low","snippet":"        if (r.jobApproved && !_spent(r.jobNonce) && !_reclaimLapsed(r)) revert BadJob();\n        if (r.jobs == 0) revert BadJob(); // the last job ran: another needs paying (retryJob)\n        --r.jobs;\n        r.jobApproved = true;\n        r.jobNonce = nonce;","title":"approveJob accepts a Permit2 nonce that is already spent, stranding that job's 0.50 $IMD allowance (keeper-only trigger; documented as unresolved in ADAPTATION.md section 5)"},{"citation":"resolved","description":"Asymmetry lens (approve/unapprove pair) crossed with the governor's setRoles. approveJob (lines 733-734) sets two ERC-1271 digests true: the Permit2 witness digest and IMD's QuoteApproval digest. _unapprove clears only the permit digest, and it derives that digest from the live `imdPayTo` rather than from the payee that was approved. Two consequences: (a) the quote digest stays valid for the life of the contract after the payment it belonged to was undone; (b) if the governor changes imdPayTo between approveJob and the reclaim, the recomputed digest is a different hash, so the original permit digest is never cleared while the allowance is still correctly reduced. No funds are at risk today: the stale permit digest carries a deadline that has already passed, which Permit2 enforces, and the quote carries expiresAt bounded to one hour. The defect is that `isValidSignature` answers 0x1626ba7e for payments the contract believes it has undone, which breaks the contract's own invariant ('valid only for the payments approveJob approved') and would become a real exposure if a future consumer of these ERC-1271 answers did not enforce the deadline. A fix would store the approved permit digest in the Request and clear both digests from stored values, but that changes src/frens/IMD6900Frens.sol and therefore the pinned addresses; reporting as info for the record.","line":745,"path":"src/frens/IMD6900Frens.sol","reproduction":"State: request 1 minted, keeper = KEEPER, governor = team wallet. Steps: (1) keeper approveJob(1, 42, T+600, q) returns (pd, qd); isValidSignature(pd) and isValidSignature(qd) both return 0x1626ba7e. (2) governor setRoles(0, 0, newPayTo). (3) warp to T+601. (4) keeper approveJob(1, 43, now+600, q): _reclaimLapsed runs _unapprove, which clears approvedDigest[_permit2Digest(42, T+600)] computed with newPayTo (a hash that was never approved). Expected: isValidSignature(pd) == 0xffffffff and the quote digest revoked. Actual: isValidSignature(pd) == 0x1626ba7e and isValidSignature(qd) == 0x1626ba7e; allowance(frens, PERMIT2) == 0.5e18 (correct). Verified with test/scratch/StaleDigest.t.sol (passes on this code, demonstrating the stale answers); without the payee change the permit digest is cleared but the quote digest still remains valid (test_KeeperApprovesTheJobPaidFor shows it is set; nothing in the contract ever unsets it).","severity":"info","snippet":"    function _unapprove(Request storage r) internal {\n        r.jobApproved = false;\n        approvedDigest[_permit2Digest(r.jobNonce, r.jobDeadline)] = false;\n        uint256 a = IERC20Min(imd).allowance(address(this), permit2);\n        SafeTransferLib.safeApprove(imd, permit2, a > JOB_PRICE ? a - JOB_PRICE : 0);\n    }","title":"Approval revocation is asymmetric with approval: _unapprove never revokes the quote digest and recomputes the permit digest from the current imdPayTo, so a setRoles(imdPayTo) during an in-flight job l"}],"hash":"9dd776b32ec3b1ec446d7e47dca6d2b4f43ce8ff3a036fe8dcd239ac0d901d95","nodeId":"b941bb98-a92a-4b9a-8e9f-99c07b1029eb","outcome":"completed","summary":"Snippets match the tree exactly, the JSON parses, and the working tree is clean apart from the ignored findings file and scratch test. The review is complete.\n\n**Outcome.** The permission model of the collection launch holds. The team wallet is owner and governor of the collection and owner of the gate; the factory, PlaceFrens, PlaceModules and the CREATE2 deployer end up with no role, and every privileged setter is behind the right guard. I found no permission bypass, no escalation path, and no confused-deputy route through held allowances. Two small defects are recorded in `.imd-findings.json`, neither of which needs a proof test because neither is high or critical.\n\n**Findings.**\n- **Low: approveJob accepts a spent Permit2 nonce.** The keeper guard holds, but the function never checks the nonce it records. A reused nonce strands that job's 0.50 $IMD allowance forever and forces a second paid job before the reveal. The requester already lists this as an unresolved residual, and the existing test in the tree reproduces it.\n- **Info: approval revocation is asymmetric.** The revoke path clears only the permit digest, never the quote digest, and derives the permit digest from the live payee. If the governor changes the job payee while a payment is in flight, the original digest is still answered as valid afterwards. No funds are at risk because Permit2 and the quote both enforce deadlines. A scratch test under test/scratch demonstrates the stale answers.\n\n**Coverage.** All 58 listed entry points have a row. 56 hold, two carry the findings above, and two external assumptions are marked unreached because they need mainnet: that the real $IMD token does not grant Permit2 an infinite allowance, and that the x402 proxy binds the transfer recipient to the witness payee. I also recorded three checked invariants: the launch keeps no role, pre-placed contracts at the planned addresses carry no poisoned state, and no held allowance can be spent by a third party.\n\n**Checks run.** The full offline suite passes at the baseline (189 passed, 5 fork suites skipped). The scratch test and the existing spent-nonce reproduction both pass. No file outside the findings file and test/scratch was changed.","treeHash":null,"usage":{"cachedInputTokens":2140156,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":60496,"runtime":"claude","turns":43,"wallClockMs":1261195}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"827152cce766ca0d","findings":[{"citation":"resolved","description":"quote() (lines 499-510) counts the floor as _unswept() $IMD + floorImd + reserve/floorRate. floorPerFren() (line 811, used by recycle and buyTreasury) counts reserve/out and floorImd/out. Neither counts address(this).balance (royalties paid to the contract by royaltyInfo, the hook's fee slice, WETH turned into ETH by unwrapWeth), which only becomes reserve when someone calls buyFloorWithEth. Anyone may call that, up to maxEthPerBuy once a block. So a fren minted before the ETH buy pays a price that leaves the ETH out, and a recycle after the buy pays a share that includes it. This breaks the stated guarantee 'nobody can mint and sell straight back to the floor for more than they paid': each ETH buy moves count/(out+count) of its value to the person who minted right before it. The exposure is largest right after resume(): setup() pauses ETH buys, so every royalty from the workers' window piles up as ETH while few frens are out. Merged from audit_economics (the treasury-buy part of its finding is covered here as well). Minimal fix: in quote() and floorPerFren(), value address(this).balance (and the WETH balance) at an ETH rate, or buy/sweep the pending ETH before a mint is quoted. This changes the creation code of src/frens and so moves FRENS_AT, a scope decision for the requester.","line":504,"path":"src/frens/IMD6900Frens.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMD6900Frens, IFrenSwapper} from \"src/frens/IMD6900Frens.sol\";\nimport {FrenPrices} from \"src/frens/FrenPrices.sol\";\n\n/// @dev A plain ERC20 with explicit allowances only (like $IMD and IMD6900 as the collection books them)\ncontract PlainToken {\n    string public name;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(string memory n) {\n        name = n;\n    }\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\"); // IMD6900 refuses a transfer of nothing\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\");\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\ncontract StubPermit2 {\n    mapping(address => mapping(uint256 => uint256)) public nonceBitmap;\n\n    function DOMAIN_SEPARATOR() external pure returns (bytes32) {\n        return keccak256(\"permit2\");\n    }\n}\n\n/// @dev The swapper as the frens see it: a fixed rate of 70,000 IMD6900 per $IMD, 1 ETH = 3,000 $IMD (the same\n///      stand-in the repository's own tests use). It pulls what it spends and mints the IMD6900 out.\ncontract StubSwapper is IFrenSwapper {\n    PlainToken internal immutable imd;\n    PlainToken internal immutable imd6900;\n    uint256 public constant RATE = 70_000;\n\n    constructor(PlainToken imd_, PlainToken imd6900_) {\n        imd = imd_;\n        imd6900 = imd6900_;\n    }\n\n    function floorRate() external pure returns (uint256) {\n        return RATE * 1e18;\n    }\n\n    function imdToImd6900(uint256 imdIn, uint256, address to) external returns (uint256 out) {\n        imd.transferFrom(msg.sender, address(this), imdIn);\n        out = imdIn * RATE;\n        imd6900.mint(to, out);\n    }\n\n    function ethToImd6900(uint256, address to) external payable returns (uint256 out) {\n        out = msg.value * 3_000 * RATE;\n        imd6900.mint(to, out);\n    }\n}\n\n/// @notice ETH that arrived at the collection (royalties, the launch hook's fee slice, unwrapped WETH) and waits for\n///         buyFloorWithEth is not in quote() nor in floorPerFren(): a mint right before the ETH buy joins the floor\n///         without paying for it, and a recycle right after takes a share of it. The round trip pays more than the\n///         mint cost, which the collection promises never happens (\"A mint never costs less than the floor it joins\").\ncontract EthPendingFloorCaptureTest is Test {\n    IMD6900Frens frens;\n    PlainToken imd;\n    PlainToken imd6900;\n    StubSwapper swapper;\n    address holder = makeAddr(\"existing holder\");\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        imd = new PlainToken(\"IMD\");\n        imd6900 = new PlainToken(\"IMD6900\");\n        PlainToken identity = new PlainToken(\"identity.md\");\n        StubPermit2 permit2 = new StubPermit2();\n        address prices = address(new FrenPrices());\n        frens = new IMD6900Frens(\n            address(this),\n            address(imd),\n            address(imd6900),\n            address(identity),\n            address(permit2),\n            makeAddr(\"x402\"),\n            makeAddr(\"payTo\"),\n            makeAddr(\"keeper\"),\n            makeAddr(\"relayer\"),\n            prices\n        );\n        swapper = new StubSwapper(imd, imd6900);\n        _rules(frens);\n        frens.sealTraits();\n        frens.setModules(address(swapper), address(0));\n        frens.setMintOpen(true);\n        imd.mint(holder, 10_000e18); // tier 3\n        imd.mint(attacker, 10_000e18); // tier 3\n        vm.prank(holder);\n        imd.approve(address(frens), type(uint256).max);\n        vm.prank(attacker);\n        imd.approve(address(frens), type(uint256).max);\n    }\n\n    /// @dev The invariant the collection states: minting and selling straight back to the floor never pays.\n    ///      Here the \"straight back\" spans one permissionless call, buyFloorWithEth, on ETH that was already here.\n    function test_MintBeforeTheEthBuyThenRecycleTakesTheFloorsFees() public {\n        // 4 frens out, their floor bought in\n        vm.prank(holder);\n        frens.requestMint(4, type(uint256).max);\n        vm.roll(block.number + 1);\n        if (frens.floorImd() != 0) frens.buyFloor(0);\n        uint256 holderFloorBefore = _floorValue();\n\n        // 0.25 ETH of royalties / hook fees arrived and waits for the next ETH buy\n        vm.deal(address(this), 0.25 ether);\n        (bool ok,) = address(frens).call{value: 0.25 ether}(\"\");\n        assertTrue(ok);\n        vm.roll(block.number + 1);\n\n        // the attacker, in one transaction: mint, buy the waiting ETH into the floor (anyone may), sell straight back\n        uint256 paid = frens.quote(1);\n        vm.startPrank(attacker);\n        frens.requestMint(1, paid);\n        frens.buyFloorWithEth(0.25 ether, 0);\n        (uint256 got6900, uint256 gotImd) = frens.recycle(5);\n        vm.stopPrank();\n        uint256 back = gotImd + got6900 * 1e18 / swapper.floorRate();\n\n        emit log_named_decimal_uint(\"paid for the mint ($IMD)\", paid, 18);\n        emit log_named_decimal_uint(\"got back from the floor ($IMD-equivalent)\", back, 18);\n        emit log_named_decimal_uint(\"the holder's floor before, per fren\", holderFloorBefore / 4, 18);\n        emit log_named_decimal_uint(\"the holder's floor after, per fren\", _floorValue() / 4, 18);\n        assertLe(back, paid, \"a mint sold straight back to the floor pays more than it cost\");\n    }\n\n    function _floorValue() internal view returns (uint256) {\n        return frens.floorImd() + frens.reserve() * 1e18 / swapper.floorRate();\n    }\n\n    /* ── the launch's trait rules (script/frens/DeployFrens.s.sol launchRules) ── */\n\n    function _fill(uint8 n, uint16 cap, uint8 tier) internal pure returns (uint16[] memory caps, uint8[] memory tiers) {\n        caps = new uint16[](n);\n        tiers = new uint8[](n);\n        for (uint8 i; i < n; ++i) {\n            (caps[i], tiers[i]) = (cap, tier);\n        }\n    }\n\n    function _rules(IMD6900Frens f) internal {\n        (uint16[] memory c, uint8[] memory t) = _fill(3, 0, 0);\n        (c[0], c[1], c[2], t[1], t[2]) = (1598, 312, 312, 2, 2);\n        f.setTraitRules(0, c, t);\n        (c, t) = _fill(13, 2222, 0);\n        (c[12], t[12]) = (56, 3);\n        f.setTraitRules(1, c, t);\n        (c, t) = _fill(4, 2222, 0);\n        (c[3], t[3]) = (222, 1);\n        f.setTraitRules(2, c, t);\n        (c, t) = _fill(3, 2222, 0);\n        (c[2], t[2]) = (103, 1);\n        f.setTraitRules(3, c, t);\n        (c, t) = _fill(6, 2222, 0);\n        f.setTraitRules(4, c, t);\n        (c, t) = _fill(3, 266, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(5, c, t);\n        (c, t) = _fill(12, 2222, 0);\n        f.setTraitRules(6, c, t);\n        (c, t) = _fill(16, 140, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(7, c, t);\n    }\n}","reproduction":"Ran test/scratch/EthPending.t.sol (the specialist's proof, unchanged) with forge 1.8.3 --offline. State: traits sealed, mint open, a stub swapper paying 70,000 IMD6900 per $IMD and 3,000 $IMD per ETH, 4 frens out to one holder, floor bought in (0.5656 $IMD per fren). 0.25 ETH is sent to receive(). Next block the attacker calls requestMint(1, quote(1)=0.6916 $IMD), then buyFloorWithEth(0.25 ether, 0), then recycle(5). Expected: back <= paid. Actual: FAIL 'a mint sold straight back to the floor pays more than it cost: 150490820000000000000 > 691600000000000000'. The holder's floor is 150.49 per fren instead of about 188.","severity":"high","snippet":"            (uint256 value,) = _unswept();\n            value += floorImd;\n            if (reserve != 0) value += reserve * 1e18 / IFrenSwapper(swapper).floorRate();","title":"Royalty/fee ETH waiting for buyFloorWithEth is left out of quote() and floorPerFren(): someone who mints before the ETH buy and recycles after it takes the existing holders' fees"},{"citation":"resolved","description":"quote() adds _unswept() extra (IMD's 0.25 job refunds, or any $IMD sent to the contract) to the floor. floorPerFren() leaves it out, so buyTreasury charges 2x a floor that is missing it, and recycle pays a share without it. The extra becomes floorImd only inside _buyFloor. buyFloor() reverts Cap when the buy is skipped (buys paused, same block, no swapper), which undoes its own sweep, so while buys are paused only a mint sweeps. Exploit: buy a treasury fren at 2x the booked floor, sweep (buyFloor, or any mint), then recycle at the swept floor. A holder who recycles before the sweep gives up their share of the extra. This shares a theme with finding 1 (backing that is not booked is priced unevenly) but it is a different gap with a different fix. Minimal fix: book the unswept $IMD (floorImd += extra) at the top of recycle() and buyTreasury(), or count it in floorPerFren(). This changes src/frens, a scope decision for the requester.","line":811,"path":"src/frens/IMD6900Frens.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMD6900Frens, IFrenSwapper} from \"src/frens/IMD6900Frens.sol\";\nimport {FrenPrices} from \"src/frens/FrenPrices.sol\";\n\n/// @dev A plain ERC20 with explicit allowances only (like $IMD and IMD6900 as the collection books them)\ncontract PlainToken {\n    string public name;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(string memory n) {\n        name = n;\n    }\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\"); // IMD6900 refuses a transfer of nothing\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\");\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\ncontract StubPermit2 {\n    mapping(address => mapping(uint256 => uint256)) public nonceBitmap;\n\n    function DOMAIN_SEPARATOR() external pure returns (bytes32) {\n        return keccak256(\"permit2\");\n    }\n}\n\n/// @dev The swapper as the frens see it: a fixed rate of 70,000 IMD6900 per $IMD, 1 ETH = 3,000 $IMD (the same\n///      stand-in the repository's own tests use). It pulls what it spends and mints the IMD6900 out.\ncontract StubSwapper is IFrenSwapper {\n    PlainToken internal immutable imd;\n    PlainToken internal immutable imd6900;\n    uint256 public constant RATE = 70_000;\n\n    constructor(PlainToken imd_, PlainToken imd6900_) {\n        imd = imd_;\n        imd6900 = imd6900_;\n    }\n\n    function floorRate() external pure returns (uint256) {\n        return RATE * 1e18;\n    }\n\n    function imdToImd6900(uint256 imdIn, uint256, address to) external returns (uint256 out) {\n        imd.transferFrom(msg.sender, address(this), imdIn);\n        out = imdIn * RATE;\n        imd6900.mint(to, out);\n    }\n\n    function ethToImd6900(uint256, address to) external payable returns (uint256 out) {\n        out = msg.value * 3_000 * RATE;\n        imd6900.mint(to, out);\n    }\n}\n\n/// @notice ETH that arrived at the collection (royalties, the launch hook's fee slice, unwrapped WETH) and waits for\n///         buyFloorWithEth is not in quote() nor in floorPerFren(): a mint right before the ETH buy joins the floor\n///         without paying for it, and a recycle right after takes a share of it. The round trip pays more than the\n///         mint cost, which the collection promises never happens (\"A mint never costs less than the floor it joins\").\ncontract UnsweptTest is Test {\n    IMD6900Frens frens;\n    PlainToken imd;\n    PlainToken imd6900;\n    StubSwapper swapper;\n    address holder = makeAddr(\"existing holder\");\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        imd = new PlainToken(\"IMD\");\n        imd6900 = new PlainToken(\"IMD6900\");\n        PlainToken identity = new PlainToken(\"identity.md\");\n        StubPermit2 permit2 = new StubPermit2();\n        address prices = address(new FrenPrices());\n        frens = new IMD6900Frens(\n            address(this),\n            address(imd),\n            address(imd6900),\n            address(identity),\n            address(permit2),\n            makeAddr(\"x402\"),\n            makeAddr(\"payTo\"),\n            makeAddr(\"keeper\"),\n            makeAddr(\"relayer\"),\n            prices\n        );\n        swapper = new StubSwapper(imd, imd6900);\n        _rules(frens);\n        frens.sealTraits();\n        frens.setModules(address(swapper), address(0));\n        frens.setMintOpen(true);\n        imd.mint(holder, 10_000e18); // tier 3\n        imd.mint(attacker, 10_000e18); // tier 3\n        vm.prank(holder);\n        imd.approve(address(frens), type(uint256).max);\n        vm.prank(attacker);\n        imd.approve(address(frens), type(uint256).max);\n    }\n\n    function test_TreasuryBuyThenSweepThenRecycleTakesUnsweptImd() public {\n        vm.prank(holder);\n        frens.requestMint(4, type(uint256).max);\n        vm.roll(block.number + 1);\n        if (frens.floorImd() != 0) frens.buyFloor(0);\n        vm.roll(block.number + 1);\n        vm.prank(holder);\n        frens.recycle(4); // one fren in the treasury\n        imd.mint(address(frens), 100e18); // $IMD arrives (refunds, a transfer): not yet swept\n        vm.roll(block.number + 1);\n        (uint256 p6900, uint256 pImd) = frens.floorPerFren();\n        uint256 cost = 2 * pImd + 2 * p6900 * 1e18 / swapper.floorRate();\n        imd6900.mint(attacker, 2 * p6900);\n        vm.startPrank(attacker);\n        imd6900.approve(address(frens), type(uint256).max);\n        frens.buyTreasury(4, 2 * p6900, 2 * pImd);\n        frens.buyFloor(0); // sweeps the 100 $IMD into floorImd and buys\n        (uint256 g6900, uint256 gImd) = frens.recycle(4);\n        vm.stopPrank();\n        uint256 back = gImd + g6900 * 1e18 / swapper.floorRate();\n        emit log_named_decimal_uint(\"treasury buy cost\", cost, 18);\n        emit log_named_decimal_uint(\"recycle back\", back, 18);\n        assertLe(back, cost, \"treasury buy + recycle pays more than it cost\");\n    }\n\n    function _floorValue() internal view returns (uint256) {\n        return frens.floorImd() + frens.reserve() * 1e18 / swapper.floorRate();\n    }\n\n    /* ── the launch's trait rules (script/frens/DeployFrens.s.sol launchRules) ── */\n\n    function _fill(uint8 n, uint16 cap, uint8 tier) internal pure returns (uint16[] memory caps, uint8[] memory tiers) {\n        caps = new uint16[](n);\n        tiers = new uint8[](n);\n        for (uint8 i; i < n; ++i) {\n            (caps[i], tiers[i]) = (cap, tier);\n        }\n    }\n\n    function _rules(IMD6900Frens f) internal {\n        (uint16[] memory c, uint8[] memory t) = _fill(3, 0, 0);\n        (c[0], c[1], c[2], t[1], t[2]) = (1598, 312, 312, 2, 2);\n        f.setTraitRules(0, c, t);\n        (c, t) = _fill(13, 2222, 0);\n        (c[12], t[12]) = (56, 3);\n        f.setTraitRules(1, c, t);\n        (c, t) = _fill(4, 2222, 0);\n        (c[3], t[3]) = (222, 1);\n        f.setTraitRules(2, c, t);\n        (c, t) = _fill(3, 2222, 0);\n        (c[2], t[2]) = (103, 1);\n        f.setTraitRules(3, c, t);\n        (c, t) = _fill(6, 2222, 0);\n        f.setTraitRules(4, c, t);\n        (c, t) = _fill(3, 266, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(5, c, t);\n        (c, t) = _fill(12, 2222, 0);\n        f.setTraitRules(6, c, t);\n        (c, t) = _fill(16, 140, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(7, c, t);\n    }\n}","reproduction":"test/scratch/Unswept.t.sol (same harness as finding 1): holder mints 4 frens, the floor is bought in, holder recycles fren 4 (it goes to the treasury), then 100e18 $IMD is sent to the collection. Next block the attacker calls buyTreasury(4, 2*f6900, 2*fImd), costing 1.13125 $IMD-equivalent, then buyFloor(0) (sweeps the 100 $IMD), then recycle(4). Expected: back <= cost. Actual: FAIL 'treasury buy + recycle pays more than it cost: 25707031250000000000 > 1131250000000000000'.","severity":"medium","snippet":"        (imd6900Part, imdPart) = (reserve / out, floorImd / out);","title":"floorPerFren() leaves out the unswept $IMD that quote() counts: buyTreasury undercharges and recycle underpays until the next sweep"},{"citation":"resolved","description":"approveJob checks whether the previous nonce is spent (_spent(r.jobNonce)) but never checks the new one. If the keeper or governor passes a nonce whose Permit2 bit is already set, the call still decrements r.jobs, sets jobApproved, lowers jobBudget by JOB_PRICE and raises the Permit2 allowance by JOB_PRICE. Permit2 can never settle that payment. _reclaimLapsed then always returns false, because _spent(r.jobNonce) is true, so releaseLapsedJob and a fresh approveJob both revert BadJob. _unswept counts the allowance in its books, so the 0.50 never reaches the floor. The request needs a second paid job (retryJob). Only the trusted keeper can trigger it, hence low. Merged from audit_flow, audit_economics and audit_permissions (the same root cause). Fix: add `if (_spent(nonce)) revert BadJob();` before line 726.","line":724,"path":"src/frens/IMD6900Frens.sol","reproduction":"With one job paid on request id (jobs=1) and Permit2 nonceBitmap(frens, 0) bit 42 set, the keeper calls approveJob(id, 42, now+600, q). Expected: BadJob. Actual: it succeeds, allowance(frens, permit2) == 0.5e18 and jobBudget == 0. After the deadline, releaseLapsedJob(id) reverts BadJob, approveJob(id, 43, ...) reverts BadJob, and balanceOf(frens) - floorImd - jobBudget == 0.5e18 forever. Ran test/FrensLaunchAdaptation.t.sol::test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection against the placed bytecode: PASS (it asserts the defect).","severity":"low","snippet":"        if (r.jobApproved && !_spent(r.jobNonce) && !_reclaimLapsed(r)) revert BadJob();\n        if (r.jobs == 0) revert BadJob(); // the last job ran: another needs paying (retryJob)\n        --r.jobs;\n        r.jobApproved = true;\n        r.jobNonce = nonce;","title":"approveJob records a Permit2 nonce that is already spent: the job's 0.50 $IMD allowance is stranded for good"},{"citation":"resolved","description":"setup() calls setParams(_, 0, 0), which pauses every floor buy. FrenSwapper.rateAverage moves only in _average, which runs only inside a swap: with imdIn == 0, _buyFloor returns early, and buyFloorWithEth reverts Cap because ethCap is 0. So until resume() the average stays at the launch-block seed. open() and resume() both call _checkSwapper, which requires avg to be within 2x of spotRate(). A pool move of more than 2x during the timelock wait (48h at least) makes both scripted steps revert for good, and the governor must call setMintOpen/setParams by hand, skipping the script's other guards (first frens, the fee exemption, the wiring check). No funds are at risk, and the script can be fixed without changing bytecode: skip the band when no buy has happened since the seed.","line":122,"path":"script/frens/DeployFrens.s.sol","reproduction":"Launch with the pool's sqrtPrice at 2^96/265 (seed of about 70,225 IMD6900 per $IMD), run setup() and firstFrens, then move the pool to sqrtPrice 2^96/384 (about 147,456, 2.1x). rateAverage is unchanged, because buyFloor reverts Cap and buyFloorWithEth reverts Cap. Make the frens a distributor and the swapper fee-exempt, then call open() and resume(). Expected: they go through. Actual: both revert 'swapper average outside spot band', since avg*2 = 140,450 < spot = 147,456, as line 122 shows.","severity":"low","snippet":"        require(avg != 0 && spot != 0 && avg * 2 >= spot && avg <= spot * 2, \"swapper average outside spot band\");","title":"DeployFrens open()/resume() check the swapper's frozen launch-block average against today's spot: after a >2x move during the pause both revert"},{"citation":"resolved","description":"_place adopts any code already at FrensPlan.SWAPPER_AT. If its seeded rateAverage is outside 2x of the launch block's spot, PlaceModules creates a replacement with plain CREATE at a different address. The contract at SWAPPER_AT can never be repaired, because only the frens contract can move its average. The swapper then does not land at 0x6900453d... 'whoever deploys', and the queued setFeeExempt(0x6900453d..., true) misses the actual swapper, so resume() waits for another 48h timelock operation. No funds are at risk. The fallback is the safer design, and README already says to read PlaceModules.swapper(). Recorded so the replacement exemption can be prepared ahead of time. Merged from audit_flow.","line":100,"path":"src/FrensPlacement.sol","reproduction":"Ran test/FrensLaunchReview.t.sol::test_Fix_LaunchReplacesASwapperSeededOffASkewedPrice: PASS. It places the swapper through the CREATE2 deployer at a pool price pushed about 10x, restores the price, deploys PlaceModules, and checks pm.swapper() != FrensPlan.SWAPPER_AT.","severity":"info","snippet":"        if (!_soundSwapper(s)) {","title":"Anyone can place the exact swapper at SWAPPER_AT in a block where they pushed the pool, forcing the CREATE fallback and voiding the queued timelock fee exemption"},{"citation":"resolved","description":"approveJob sets two digests in approvedDigest (lines 733-734). _unapprove clears only the permit digest, and it recomputes that digest from the live imdPayTo. After setRoles changes imdPayTo, the original permit digest is never cleared, and the quote digest is never cleared at all. isValidSignature keeps returning the magic value for payments the contract treats as undone. There is no exploitable effect: Permit2 enforces the permit's deadline, which has already passed, and the quote's expiresAt is capped at one hour. Merged from audit_permissions. Fix if the code is ever reopened: store the approved digests in the Request and clear both.","line":747,"path":"src/frens/IMD6900Frens.sol","reproduction":"The keeper calls approveJob(1, 42, T+600, q), which returns (pd, qd). The governor calls setRoles(0, 0, newPayTo). Warp to T+601; the keeper calls approveJob(1, 43, ...) (it needs a second paid job, or the reclaim path via releaseLapsedJob after the reveal). _reclaimLapsed -> _unapprove clears the digest built from newPayTo. isValidSignature(pd, '') and isValidSignature(qd, '') still return 0x1626ba7e (expected 0xffffffff).","severity":"info","snippet":"        approvedDigest[_permit2Digest(r.jobNonce, r.jobDeadline)] = false;","title":"_unapprove never clears the quote digest and recomputes the permit digest from the current imdPayTo"}],"hash":"be3876f3914ec424cc9fb7bda9d7c775082790137a2734e37aa2a207ea862347","nodeId":"ca144136-459e-421d-b2b9-94a01c2b99d3","outcome":"completed","summary":"The review is in `.imd-findings.json`: 6 findings (1 high, 1 medium, 2 low, 2 info) and 58 coverage rows. That covers all 56 entry points plus 2 launch checks. No repository file was changed. I deleted my scratch tests, and their code is stored inside the findings file.\n\n**Findings that can reopen the work**\n1. **High: ETH waiting to be bought into the floor is left out of the price** (`src/frens/IMD6900Frens.sol:504`). Royalties and hook fees sit in the contract as ETH until someone calls `buyFloorWithEth`. Neither `quote()` nor `floorPerFren()` counts that ETH. So anyone can mint, trigger the ETH buy, and recycle, taking a share of fees owed to existing holders. I ran the economics specialist's proof and it fails for the stated reason: 150.49 back for a 0.69 mint. It is attached to the finding.\n2. **Medium: `floorPerFren()` leaves out unswept $IMD that `quote()` counts** (`:811`). This lets someone buy a fren from the treasury cheaply, sweep the waiting $IMD into the floor, and recycle at the higher price. My test fails on the current code: 25.71 back for a 1.13 treasury buy. It is attached as the proof.\n3. **Low: `approveJob` accepts a Permit2 nonce that is already spent** (`:724`), which strands that job's 0.50 $IMD for good. Three specialists reported this; I merged them into one finding. Only the trusted keeper can trigger it. The repository's own test that demonstrates it passes, confirming the defect is present.\n4. **Low: `open()` and `resume()` in `script/frens/DeployFrens.s.sol` can lock themselves out** (`:122`). While floor buys are paused, the swapper's price average stays fixed at the launch-block value. If the pool moves more than 2x before resuming, both steps revert, and the governor has to bypass the script's guards. I confirmed this by reading the code; I did not run a test for it.\n\n**Info only**\n- Anyone can place the swapper at its planned address in a block where they pushed the pool price; the launch then creates a replacement at another address. The queued timelock fee exemption would then miss it, delaying `resume()` by another 48h. The repository's test for this passes. No funds are at risk.\n- After a job payment is undone, `isValidSignature` still accepts its quote approval, and its permit approval too if the payee was changed meanwhile. Both have expired by then, so nothing can use them.\n\nEvery specialist finding reproduced; I dropped none. My own pass over the minter, worker gate, swapper, placement contracts and collection found nothing new. The tests checking the gas limit (`test_EachLaunchFitsOneTransaction`) and that the pinned creation code matches the sources (`test_CodeIsWhatTheSourcesBuild`) pass with forge 1.8.3.\n\nThe fixes for findings 1–3 change `src/frens`, which moves the collection and swapper addresses. Whether to fix them is your decision. Finding 4 can be fixed in the script alone.","treeHash":null,"usage":{"cachedInputTokens":996048,"inputTokens":24,"model":"claude-opus-5-5","outputTokens":19405,"runtime":"claude","turns":14,"wallClockMs":941850}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1a7ecd03bd365366","findings":[{"citation":"resolved","description":"Execution trace of the job-payment state machine. approveJob(requestId, nonce, deadline, q) only checks the request's PREVIOUS nonce (line 724: `if (r.jobApproved && !_spent(r.jobNonce) && !_reclaimLapsed(r)) revert BadJob();`) and never checks `_spent(nonce)` for the nonce it is about to record. If the keeper (or the governor, who passes onlyKeeper too) supplies a nonce whose bit is already set in Permit2's nonceBitmap(frens, nonce>>8), the call still does `--r.jobs; r.jobApproved = true; r.jobNonce = nonce; jobBudget -= JOB_PRICE; safeApprove(imd, permit2, a + JOB_PRICE)`. From then on every path that could undo the approval is closed: `_reclaimLapsed` (line 672) returns false because `_spent(r.jobNonce)` is true, so `releaseLapsedJob` reverts BadJob and a later `approveJob` for the same request also reverts BadJob (`r.jobs == 0` after the first approval, and retryJob only adds a NEW 0.50). Permit2 itself rejects the permit (nonce used), so the 0.50 $IMD is never pulled either. Because `_unswept()` counts `allowance(this, permit2)` as part of the books, the 0.50 is not swept into the floor and is permanently unreachable: not floorImd, not jobBudget, not payable to IMD. The request's frens can still be revealed only if someone pays another 0.50 via retryJob. Trigger is a trusted role's mistake (keeper nonce reuse across restarts/pending jobs), which is why this is low rather than medium; it is listed as 'unresolved in pinned bytecode' in ADAPTATION.md item 5 and this review confirms it is still present and reproducible in the tree as delivered. Fix (changes the pinned collection, so a scope decision): add `if (_spent(nonce)) revert BadJob();` before line 726, or let `_reclaimLapsed` treat a nonce that was spent BEFORE this approval as reclaimable by recording `nonceBitmap` state at approval time.","line":728,"path":"src/frens/IMD6900Frens.sol","reproduction":"State: a sealed, wired collection (the exact placed one), one request minted (jobs=1, jobBudget=0.5). Permit2 has nonce 42 of the collection already spent (any earlier payment used it). Call, as KEEPER: approveJob(id, 42, block.timestamp+600, q). Expected: revert BadJob (nonce unusable). Actual: succeeds; allowance(frens, permit2) == 0.5e18, jobBudget == 0. Then warp past the deadline and call releaseLapsedJob(id): reverts BadJob. Call approveJob(id, 43, now+600, q) as KEEPER: reverts BadJob. imd.balanceOf(frens) - floorImd - jobBudget == 0.5e18 forever. The in-tree test test/FrensLaunchAdaptation.t.sol:test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection executes exactly this sequence against the pinned creation code and passes, i.e. asserts the defect is present.","severity":"low","snippet":"        r.jobNonce = nonce;","title":"approveJob accepts an already-spent Permit2 nonce and strands that job's 0.50 $IMD allowance for good"},{"citation":"resolved","description":"First-principles note on the launch control flow, reported because its cost is lower than the README's wording suggests, not because the fallback is wrong (reverting the launch would be worse). `_place` (line 32) adopts whatever already sits at FrensPlan.SWAPPER_AT; `_soundSwapper` then rejects it when its constructor-seeded rateAverage is more than 2x off the pool's spot at launch and the constructor creates a replacement with plain CREATE. Nothing can ever repair the contract at SWAPPER_AT: its average moves only through floor buys, which only the frens contract can trigger, and the frens are never wired to it. So a third party who, in one bundle, pushes the IMD6900/$IMD pool by >2x, submits the exact salt+initcode to the standard CREATE2 deployer, and unwinds the push, permanently displaces the swapper off 0x6900453d... Cost: the pair hook's 6.9% fee on the output of each leg of the push (verified from the hook's verified source: _afterSwap takes fee() of the output for non-exempt callers) plus ~1.5M gas. Consequence (verified on mainnet at block 26149211): timelock operation 0x6ebf60b5... is already scheduled (getTimestamp = 1791649787, not done) and its second call is setFeeExempt(0x6900453d..., true); PlaceModules.swapper() would be a different address, so resume() refuses ('actual swapper is not fee-exempt') until a new 48h operation (getMinDelay = 172800) targeting the actual address is queued after the launch. Floor buys stay paused for at least those 48h; no funds are at risk. Also note the band is only checked against the launch block's spot, so a pre-placed swapper skewed by up to 2x is adopted; a 2x-low average over-values the reserve in quote() (mint price up to 2x the floor component) until ~64 full buys decay it. Nothing to change in the pinned code; the operational answer in README (always read PlaceModules.swapper(), queue the exemption for the actual address) is correct and should be treated as the expected path rather than an edge case, and a replacement operation should be prepared immediately after the launch lands.","line":100,"path":"src/FrensPlacement.sol","reproduction":"Offline, on the exact placed collection: etch the standard deployer, set the pool stub price to 2^96/26 (IMD6900 ~100x dearer), have an unrelated EOA call CREATE2_DEPLOYER with SWAPPER_SALT ++ FrensCode.SWAPPER ++ abi.encode(POOL_MANAGER, IMD, IMD6900, FRENS_AT, PAIR_HOOK, POOL4_HOOK); set the price back to 2^96/265 and roll one block; deploy PlaceModules(PlaceFrens). Expected by the plan: pm.swapper() == 0x6900453deFAc8Bb12eabdcf57CCC5a14E7628AeE. Actual: pm.swapper() == computeCreateAddress(pm, 1), a fresh address, while the skewed contract keeps the planned address forever. Then, with feeExempt(SWAPPER_AT)=true (what the queued batch grants) and feeExempt(pm.swapper())=false, DeployFrens.resume() reverts 'actual swapper is not fee-exempt'. Both halves are already executed by in-tree tests test/FrensLaunchReview.t.sol:test_Fix_LaunchReplacesASwapperSeededOffASkewedPrice and test/FrensLaunchAdaptation.t.sol:test_SetupUsesTheLaunchsReplacementSwapper, which pass.","severity":"info","snippet":"        if (!_soundSwapper(s)) {\n            assembly (\"memory-safe\") {\n                s := create(0, add(init, 32), mload(init))\n            }","title":"Anyone can burn the planned swapper address before the launch for the price of one sandwich bundle, forcing the CREATE fallback and voiding the already-queued timelock fee exemption"}],"hash":"db105aedc3878e950f41a5056efef9393653fd0cd343b172be497fa6066025ea","nodeId":"82c91496-e5a3-4188-8006-d6c0c6f852db","outcome":"completed","summary":"Review complete. The findings file is written and validated: 2 findings, all 56 entry points covered plus three invariant rows, and both snippets match the tree at the cited lines.\n\n**Outcome.** In my area (Execution Trace, Periphery, First Principles) the collection launch holds up. I traced every entry point's guards, state changes and failure paths, ran the full offline suite (189 passed) and all five live mainnet fork suites (24 tests, including the whole post-launch road), and verified the external dependencies against their verified sources and live state rather than the mocks.\n\n**Findings written to `.imd-findings.json`:**\n\n- **Low, approveJob accepts a spent Permit2 nonce.** `src/frens/IMD6900Frens.sol:728` records the new nonce without checking it; afterwards no path can reclaim the 0.50 $IMD allowance, and Permit2 will never pull it. Only a keeper or governor mistake triggers it. It is the residual defect ADAPTATION.md lists as unresolved, confirmed still present and reproduced by the in-tree test.\n- **Info, planned swapper address can be burned cheaply before launch.** `src/FrensPlacement.sol:100`: a pre-placement seeded in a pushed block is permanently unrepairable, so the launch's CREATE fallback fires and the timelock operation already queued on mainnet exempts the wrong address. No funds at risk, a 48-hour delay. The README documents the recovery; I report it because the cost is one sandwich bundle, so it should be treated as the expected path.\n\n**Leads I closed as not defects, with evidence:**\n\n- `$IMD` does not grant Permit2 an infinite allowance on mainnet, so the `allowance` term in the books cannot overflow.\n- The pair hook takes its fee on output through an after-swap delta and exempts by swap caller, matching the swapper's assumptions. It forbids exact output, which only the POOL4 leg uses.\n- The strategy token allows pool-manager takes and distributor transfers, so recycle and treasury buys work once the batch lands and are correctly paused before it.\n- The launch hook force-pushes ETH to its fee address, so the collection's empty `receive()` suffices.\n\n**Not reached in depth:** the art launch, renderer and Uniswap internals, which are outside the assigned area.","treeHash":null,"usage":{"cachedInputTokens":3036359,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":84496,"runtime":"claude","turns":45,"wallClockMs":1722179}},{"artifacts":[],"attempt":1,"bundleHash":"b7ad63e7b86e0a81404bb01980d8b784f0bb62ccb81d80c867952be07b968cf5","device":"ca0f4e05e864c332","findings":[],"hash":"e062878ea43a049ae5cb4833ea92052dec464781a9843b410a93df589cd83a91","nodeId":"c6400105-8885-4709-ae1d-977d121e09f4","outcome":"completed","summary":"Updated only [launch.json](/root/.identitymd/work/ea109756-4be9-42b7-8df9-a3457e934599/c6400105-8885-4709-ae1d-977d121e09f4/launch.json), preserving `PlaceFrens([])` followed by `PlaceModules([\"$contract:PlaceFrens\"])` and clarifying the separate art launch.\n\nSchema and constructor ABI checks passed. Offline `forge build` passed; `forge test` reported 189 passed, 0 failed, 5 skipped.","treeHash":"88411ef34421358f6909a628828ef7661b2d6e37","usage":{"cachedInputTokens":743424,"inputTokens":73079,"model":"gpt-6-astra","outputTokens":5768,"runtime":"codex","turns":7,"wallClockMs":465614}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"09078b7cdfb673fe","findings":[{"citation":"resolved","description":"quote() prices a mint at no less than its share of the floor, and the floor it counts is the unswept $IMD, floorImd and the reserve valued at floorRate(). It does not count the ETH the contract already holds: marketplace royalties (royaltyInfo pays this contract), the launch hook's fee slice (the timelock batch moves it here) and WETH unwrapped by unwrapWeth() all sit as address(this).balance until someone calls buyFloorWithEth(), which anyone may do, at most maxEthPerBuy (0.25 ETH) per block. floorPerFren() (what recycle pays and half of what buyTreasury charges) does not count that ETH either. So between the ETH's arrival and its purchase, a fren can be bought into the floor at a price that excludes the ETH and sold back at one that includes it. The collection's own invariant ('A mint never costs less than the floor it joins', 'nobody can mint and sell straight back to the floor for more than they paid', test_QuoteCountsUnsweptImd for the $IMD case) is broken for the ETH leg, and the unprivileged caller takes count/(out+count) of every ETH buy's value from the existing holders for 0.50*count/(out+count) $IMD and gas. The three calls (requestMint, buyFloorWithEth, recycle) can be one transaction or a bundle around the keeper's or arb bot's own buyFloorWithEth, and the frens can be held across many blocks of buys and recycled once. It is worst exactly where the runbook puts it: setup() pauses buys (maxEthPerBuy = 0) until the timelock batch, so every royalty and fee of the workers' window accumulates as ETH and is bought in over the blocks after resume(), when the public mint opens and out is a few hundred at most (count = 69 at tier 3, or 69 one-fren requests at tier 0). The same gap prices buyTreasury (2 x floorPerFren, ETH excluded) below a recycle after the buy. There is no operational fix inside the pinned bytecode: royalties always arrive here as ETH/WETH, pausing ETH buys only grows the backlog, and lowering maxEthPerBuy only spreads the same take over more blocks. The fix is in IMD6900Frens: count address(this).balance (and the WETH balance) in quote() at a rate the swapper reports for ETH, or sweep the ETH into the floor inside requestMintFor before quoting, as the $IMD sweep already does; it changes the collection's creation code and therefore FrensPlan's 0x6900 address, which is the requester's decision.","line":504,"path":"src/frens/IMD6900Frens.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMD6900Frens, IFrenSwapper} from \"src/frens/IMD6900Frens.sol\";\nimport {FrenPrices} from \"src/frens/FrenPrices.sol\";\n\n/// @dev A plain ERC20 with explicit allowances only (like $IMD and IMD6900 as the collection books them)\ncontract PlainToken {\n    string public name;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(string memory n) {\n        name = n;\n    }\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\"); // IMD6900 refuses a transfer of nothing\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        require(a != 0, \"zero\");\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\ncontract StubPermit2 {\n    mapping(address => mapping(uint256 => uint256)) public nonceBitmap;\n\n    function DOMAIN_SEPARATOR() external pure returns (bytes32) {\n        return keccak256(\"permit2\");\n    }\n}\n\n/// @dev The swapper as the frens see it: a fixed rate of 70,000 IMD6900 per $IMD, 1 ETH = 3,000 $IMD (the same\n///      stand-in the repository's own tests use). It pulls what it spends and mints the IMD6900 out.\ncontract StubSwapper is IFrenSwapper {\n    PlainToken internal immutable imd;\n    PlainToken internal immutable imd6900;\n    uint256 public constant RATE = 70_000;\n\n    constructor(PlainToken imd_, PlainToken imd6900_) {\n        imd = imd_;\n        imd6900 = imd6900_;\n    }\n\n    function floorRate() external pure returns (uint256) {\n        return RATE * 1e18;\n    }\n\n    function imdToImd6900(uint256 imdIn, uint256, address to) external returns (uint256 out) {\n        imd.transferFrom(msg.sender, address(this), imdIn);\n        out = imdIn * RATE;\n        imd6900.mint(to, out);\n    }\n\n    function ethToImd6900(uint256, address to) external payable returns (uint256 out) {\n        out = msg.value * 3_000 * RATE;\n        imd6900.mint(to, out);\n    }\n}\n\n/// @notice ETH that arrived at the collection (royalties, the launch hook's fee slice, unwrapped WETH) and waits for\n///         buyFloorWithEth is not in quote() nor in floorPerFren(): a mint right before the ETH buy joins the floor\n///         without paying for it, and a recycle right after takes a share of it. The round trip pays more than the\n///         mint cost, which the collection promises never happens (\"A mint never costs less than the floor it joins\").\ncontract EthPendingFloorCaptureTest is Test {\n    IMD6900Frens frens;\n    PlainToken imd;\n    PlainToken imd6900;\n    StubSwapper swapper;\n    address holder = makeAddr(\"existing holder\");\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        imd = new PlainToken(\"IMD\");\n        imd6900 = new PlainToken(\"IMD6900\");\n        PlainToken identity = new PlainToken(\"identity.md\");\n        StubPermit2 permit2 = new StubPermit2();\n        address prices = address(new FrenPrices());\n        frens = new IMD6900Frens(\n            address(this),\n            address(imd),\n            address(imd6900),\n            address(identity),\n            address(permit2),\n            makeAddr(\"x402\"),\n            makeAddr(\"payTo\"),\n            makeAddr(\"keeper\"),\n            makeAddr(\"relayer\"),\n            prices\n        );\n        swapper = new StubSwapper(imd, imd6900);\n        _rules(frens);\n        frens.sealTraits();\n        frens.setModules(address(swapper), address(0));\n        frens.setMintOpen(true);\n        imd.mint(holder, 10_000e18); // tier 3\n        imd.mint(attacker, 10_000e18); // tier 3\n        vm.prank(holder);\n        imd.approve(address(frens), type(uint256).max);\n        vm.prank(attacker);\n        imd.approve(address(frens), type(uint256).max);\n    }\n\n    /// @dev The invariant the collection states: minting and selling straight back to the floor never pays.\n    ///      Here the \"straight back\" spans one permissionless call, buyFloorWithEth, on ETH that was already here.\n    function test_MintBeforeTheEthBuyThenRecycleTakesTheFloorsFees() public {\n        // 4 frens out, their floor bought in\n        vm.prank(holder);\n        frens.requestMint(4, type(uint256).max);\n        vm.roll(block.number + 1);\n        if (frens.floorImd() != 0) frens.buyFloor(0);\n        uint256 holderFloorBefore = _floorValue();\n\n        // 0.25 ETH of royalties / hook fees arrived and waits for the next ETH buy\n        vm.deal(address(this), 0.25 ether);\n        (bool ok,) = address(frens).call{value: 0.25 ether}(\"\");\n        assertTrue(ok);\n        vm.roll(block.number + 1);\n\n        // the attacker, in one transaction: mint, buy the waiting ETH into the floor (anyone may), sell straight back\n        uint256 paid = frens.quote(1);\n        vm.startPrank(attacker);\n        frens.requestMint(1, paid);\n        frens.buyFloorWithEth(0.25 ether, 0);\n        (uint256 got6900, uint256 gotImd) = frens.recycle(5);\n        vm.stopPrank();\n        uint256 back = gotImd + got6900 * 1e18 / swapper.floorRate();\n\n        emit log_named_decimal_uint(\"paid for the mint ($IMD)\", paid, 18);\n        emit log_named_decimal_uint(\"got back from the floor ($IMD-equivalent)\", back, 18);\n        emit log_named_decimal_uint(\"the holder's floor before, per fren\", holderFloorBefore / 4, 18);\n        emit log_named_decimal_uint(\"the holder's floor after, per fren\", _floorValue() / 4, 18);\n        assertLe(back, paid, \"a mint sold straight back to the floor pays more than it cost\");\n    }\n\n    function _floorValue() internal view returns (uint256) {\n        return frens.floorImd() + frens.reserve() * 1e18 / swapper.floorRate();\n    }\n\n    /* ── the launch's trait rules (script/frens/DeployFrens.s.sol launchRules) ── */\n\n    function _fill(uint8 n, uint16 cap, uint8 tier) internal pure returns (uint16[] memory caps, uint8[] memory tiers) {\n        caps = new uint16[](n);\n        tiers = new uint8[](n);\n        for (uint8 i; i < n; ++i) {\n            (caps[i], tiers[i]) = (cap, tier);\n        }\n    }\n\n    function _rules(IMD6900Frens f) internal {\n        (uint16[] memory c, uint8[] memory t) = _fill(3, 0, 0);\n        (c[0], c[1], c[2], t[1], t[2]) = (1598, 312, 312, 2, 2);\n        f.setTraitRules(0, c, t);\n        (c, t) = _fill(13, 2222, 0);\n        (c[12], t[12]) = (56, 3);\n        f.setTraitRules(1, c, t);\n        (c, t) = _fill(4, 2222, 0);\n        (c[3], t[3]) = (222, 1);\n        f.setTraitRules(2, c, t);\n        (c, t) = _fill(3, 2222, 0);\n        (c[2], t[2]) = (103, 1);\n        f.setTraitRules(3, c, t);\n        (c, t) = _fill(6, 2222, 0);\n        f.setTraitRules(4, c, t);\n        (c, t) = _fill(3, 266, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(5, c, t);\n        (c, t) = _fill(12, 2222, 0);\n        f.setTraitRules(6, c, t);\n        (c, t) = _fill(16, 140, 1);\n        (c[0], t[0]) = (2222, 0);\n        f.setTraitRules(7, c, t);\n    }\n}","reproduction":"State: the collection set up and open, swapper wired (the test's stub pays 70,000 IMD6900 per $IMD and 3,000 $IMD per ETH, as the repository's MockSwapper does), 4 frens out held by one wallet, their floor bought in (floor 0.566 $IMD a fren). 0.25 ETH of royalties arrives at the collection (a plain transfer to receive()). Next block, the attacker (tier 3, any wallet with 690 $IMD) sends one transaction: (1) paid = quote(1) = 0.6916 $IMD; requestMint(1, paid); (2) buyFloorWithEth(0.25 ether, 0) (anyone may; lastEthBuyBlock allows it); (3) recycle(5). Expected: the recycle returns at most what the mint cost (the stated invariant). Actual: recycle pays 1/5 of the whole floor including the 750 $IMD-equivalent the ETH bought: 150.49 $IMD-equivalent for a 0.69 $IMD mint; the holder's 4 frens are worth 150.49 each instead of 188.07. Proof: test/scratch/EthPendingFloorCapture.t.sol fails on the current code with 'a mint sold straight back to the floor pays more than it cost: 150490820000000000000 > 691600000000000000'.","severity":"high","snippet":"            (uint256 value,) = _unswept();\n            value += floorImd;\n            if (reserve != 0) value += reserve * 1e18 / IFrenSwapper(swapper).floorRate();","title":"ETH waiting for buyFloorWithEth is in nobody's floor: a mint before the ETH buy and a recycle after it take the holders' fees"},{"citation":"resolved","description":"setup() pauses floor buys (maxImdPerBuy = maxEthPerBuy = 0) until the timelock batch lands, and open() and resume() both require the swapper's rateAverage to be within 2x of spotRate() (_checkSwapper). But rateAverage moves only in FrenSwapper._average, which runs only inside a floor buy, and no floor buy can run while the caps are 0 (_buyFloor returns false with imdIn 0, buyFloorWithEth reverts Cap). So from setup() until resume() the average is exactly the launch block's seed, and the check compares a weeks-old reading with today's pool: a legitimate move of more than 2x in either direction over the timelock's 48h or longer (ordinary for a small-cap pool) makes open() and resume() revert with 'swapper average outside spot band', and nothing in the scripted path can bring the average back into the band because the only thing that moves it is the buys resume() would turn on. The workers' window and the floor then depend on the governor calling setMintOpen/setParams by hand, outside the guards the runbook relies on (firstFrens before opening, the actual swapper's fee exemption, the wiring check). The check is documented as a sanity check, but the runbook makes it the only path. Fix in the script (no bytecode change): in open() and resume(), compare the average with the price at the launch block (the seed is that by construction) or skip the band when no buy has happened since the seed (averagedAt equals the swapper's creation block), and keep the band for the later-block skew check it was meant for; or re-run the band only in setup().","line":122,"path":"script/frens/DeployFrens.s.sol","reproduction":"State: the collection launched with the pool at sqrtPrice 2^96/265 (about 70,225 IMD6900 per $IMD, the swapper's seed), setup() run (buys paused), firstFrens done (2 frens to IMD6900), then 48h and 14,400 blocks pass with no buy possible (buyFloor and buyFloorWithEth revert Cap; rateAverage still equals the seed). The pool drifts to sqrtPrice 2^96/384 (about 147,456 per $IMD, 2.1x the seed). The frens is now a distributor and the swapper fee-exempt. Call open(): expected the window to open (firstFrens, sealing and wiring all hold); actual revert 'swapper average outside spot band'. Call resume(): same revert; maxImdPerBuy stays 0 and mintOpen false. test/scratch/BandCheckWhilePaused.t.sol reproduces exactly this sequence (it passes: it asserts the reverts).","severity":"low","snippet":"    function _checkSwapper(address swapper) internal view {\n        if (POOL_MANAGER.code.length == 0) return;\n        FrenSwapper s = FrenSwapper(payable(swapper));\n        uint256 avg = s.rateAverage();\n        uint256 spot = s.spotRate();\n        require(avg != 0 && spot != 0 && avg * 2 >= spot && avg <= spot * 2, \"swapper average outside spot band\");\n    }","title":"open() and resume() cannot pass their 2x price band after the paused weeks: the average they check is frozen at the launch seed until resume() itself re-enables buys"},{"citation":"resolved","description":"approveJob checks the previous nonce's state (_spent(r.jobNonce)) but not the new one: a nonce whose bit is already set in Permit2's nonceBitmap for this contract (reused across restarts, or across two pending jobs) is stored as r.jobNonce, the allowance to Permit2 grows by JOB_PRICE and jobBudget shrinks by it. Permit2 refuses the payment (nonce used), and every reclaim path treats a spent nonce as a payment taken: _reclaimLapsed returns false, so releaseLapsedJob reverts BadJob after the reveal and approveJob with a fresh nonce reverts BadJob for want of r.jobs. The 0.50 $IMD stays in the Permit2 allowance forever, counted in the books (_unswept excludes it) so it never joins the floor, and the request needs another paid job (retryJob) to be built. ADAPTATION.md lists this as the remaining unresolved contract defect; the trigger is a keeper error, not an outside actor, so this is low. Repeating it here so the coverage record is complete: the pinned bytecode has no guard, and the README's nonce discipline is the only protection.","line":728,"path":"src/frens/IMD6900Frens.sol","reproduction":"State: a request id with one job paid (jobs = 1). Permit2's bitmap for the collection already has bit 42 set (the repository's test does permit2.spend(frens, 42)). The keeper calls approveJob(id, 42, now + 600, q). Expected: BadJob (a spent nonce can never be settled). Actual: it succeeds, allowance(frens, permit2) = 0.5e18, jobBudget = 0; after the deadline releaseLapsedJob(id) reverts BadJob and approveJob(id, 43, ...) reverts BadJob; balanceOf(frens) - floorImd - jobBudget = 0.5e18 stranded. test/FrensLaunchAdaptation.t.sol test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection shows it on the placed bytecode.","severity":"low","snippet":"        if (r.jobApproved && !_spent(r.jobNonce) && !_reclaimLapsed(r)) revert BadJob();\n        if (r.jobs == 0) revert BadJob(); // the last job ran: another needs paying (retryJob)\n        --r.jobs;\n        r.jobApproved = true;\n        r.jobNonce = nonce;","title":"approveJob accepts a Permit2 nonce that is already spent: the request's 0.50 $IMD is booked as an allowance nothing can take or release (known, unresolved)"}],"hash":"f6fba6ca032ee84f003a68905c94bae120958343263458e253c8a5b21a24778f","nodeId":"e00abeea-35ba-43f0-aee8-db1724305909","outcome":"completed","summary":"Review complete. The findings file is written and verified; no source, configuration or test file outside the disposable scratch directory was changed.\n\n**What I found**\n\n- **High: fee ETH waiting in the collection is in nobody's floor.** `quote()` and `floorPerFren()` count the reserve, the waiting $IMD and unswept $IMD, but not the ETH already held from royalties, the launch hook's fee slice and unwrapped WETH. Anyone may call `buyFloorWithEth`, so an unprivileged minter can mint, trigger the ETH buy, and recycle in one transaction or bundle. The proof test shows a 0.69 $IMD mint returning about 150 $IMD-equivalent out of a single 0.25 ETH buy with four frens out. It is worst right after `resume()`, when the paused weeks' royalties are bought in while supply is small. There is no operational fix in the pinned bytecode; the quote must count the ETH, which moves the 0x6900 address. That is the requester's decision, stated as such in the finding.\n- **Low: the runbook's price band can lock itself out.** Buys are paused from `setup()` until `resume()`, and the swapper's average only moves through buys, so it stays at the launch seed. If the pool drifts more than 2x in the meantime, `open()` and `resume()` both revert and the governor must act outside the script's guards. Reproduced in a scratch test. Script-only fix.\n- **Low: the acknowledged spent-nonce strand in `approveJob`,** repeated so the coverage record is complete, with the repository's own reproduction cited.\n\n**Coverage**\n\nAll 56 verifier entry points have a row, plus four invariant rows. The Economic Security, Invariant and Flow Gap checklists were applied to the collection, swapper, minter, gate, placement contracts and the deploy script. Verified as holding: $IMD books conservation across every path, sandwich protection of the limited floor buys, the dearest-rate floor valuation, the job payment lifecycle, the worker window and WL, the placement's pre-placement and fallback behaviour.\n\n**Not reachable offline**\n\nWhether the launch hook pays its fee slice with a plain value transfer or a function call (an empty `receive()` rejects the latter) depends on mainnet code not in the tree. Fork tests and the pair hook's `fee()` unit were not re-verified here.","treeHash":null,"usage":{"cachedInputTokens":3933599,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":93311,"runtime":"claude","turns":59,"wallClockMs":1834289}}],"verification":[{"checks":[{"durationMs":153606,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.30\nSolc 0.8.30 finished in 153.38s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n   --> test/FrensPlacement.t.sol:786:9:\n    |\n786 |         uint256 s = _find(j, key, 0) + key.length;\n    |         ^^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/FrensPlacement.t.sol:477:5:\n    |\n477 |     DeployFrens s;\n    |     ^^^^^^^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to view\n   --> test/FrensLaunchAdaptation.t.sol:184:5:\n    |\n184 |     function test_PlacedReportsTheLaunchModules() public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/frens/IMD6900Frens.sol:953:66\n    │\n953 │         return (ITransferValidator721.validateTransfer.selector, true);\n    │                                                                  ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:39\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                       ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:55\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:77\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                                             ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:68:9\n   │\n68 │         address imd6900_,\n   │         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:71:9\n   │\n71 │         address imdPoolHook_\n   │         ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:67:9\n   │\n67 │         address imd_,\n   │         ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:70:9\n   │\n70 │         address pairHook_,\n   │         ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:69:9\n   │\n69 │         address frens_,\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-events-access-control]: `keeper` is changed without an event but is used for access control\n    ╭▸ src/frens/IMD6900Frens.sol:998:36\n    │\n998 │         if (keeper_ != address(0)) keeper = keeper_;\n    │                                    ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `governor` is changed without an event but is used for access control\n     ╭▸ src/frens/IMD6900Frens.sol:1064:9\n     │\n1064 │         governor = g;\n     │         ━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:54:13\n   │\n54 │ ┏             abi.encodePacked(\n55 │ ┃                 FrensCode.FRENS,\n56 │ ┃                 abi.encode(\n57 │ ┃                     FrensPlan.OWNER,\n   ‡ ┃\n68 │ ┃             )\n   │ ┗━━━━━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:118:35\n    │\n118 │             if (r.length == 68 && bytes4(r) == Quoted.selector) {\n    │                                   ━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:150:16\n    │\n150 │         return uint160(zeroForOne ? uint256(p) * 1e18 / f : uint256(p) * f / 1e18);\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:93:29\n   │\n93 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n94 │ ┃             FrensCode.SWAPPER,\n95 │ ┃             abi.encode(\n96 │ ┃                 FrensPlan.POOL_MANAGER, FrensPlan.IMD, FrensPlan.IMD6900, f, FrensPlan.PAIR_HOOK, FrensPlan.POOL4_HOOK\n97 │ ┃             )\n98 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:109:13\n    │\n109 │ ┏             abi.encodePacked(\n110 │ ┃                 FrensCode.MINTER, abi.encode(FrensPlan.POOL_MANAGER, f, FrensPlan.POOL4_HOOK, FrensPlan.PAIR_HOOK)\n111 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:115:13\n    │\n115 │             abi.encodePacked(FrensCode.GATE, abi.encode(FrensPlan.OWNER, f, FrensPlan.IDENTITY, FrensPlan.IMD6900))\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:138:79\n    │\n138 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                 ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:25\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:33\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:148:39\n    │\n148 │                     amountSpecified: -int256(imdIn),\n    │                                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:35\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:43\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:158:70\n    │\n158 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: int256(imdOut), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:25\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:33\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:23\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:31\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenMinter.sol:165:9\n    │\n165 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/FrenMinter.sol:183:29\n    │\n183 │             if (!withEth && cost > a) continue;\n    │                             ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:178:17\n    │\n178 │             try frens.quote(n) returns (uint256 q) {\n    │                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:22\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:51\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:84\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:184:22\n    │\n184 │             if (n <= frens.maxMint(_tier(withEth ? a : a - cost, b, c))) return n;\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:59:29\n   │\n59 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n60 │ ┃             '{\"name\":\"Worker Fren #',\n61 │ ┃             tokenId.toString(),\n62 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n68 │ ┃             \"}\"\n69 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:75:29\n   │\n75 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n76 │ ┃             '{\"name\":\"Worker Fren #',\n77 │ ┃             tokenId.toString(),\n78 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n82 │ ┃             '\",\"attributes\":[{\"trait_type\":\"Status\",\"value\":\"Unrevealed\"}]}'\n83 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:125:54\n    │\n125 │                     bytes memory out = new bytes(j - start);\n    │                                                      ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:124:21\n    │\n124 │                 if (k == i) {\n    │                     ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/frens/WorkerFrensRenderer.sol:158:17\n    │\n158 │ ┏                 abi.encodePacked(\n159 │ ┃                     '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 84 84\" width=\"840\" height=\"840\">',\n160 │ ┃                     '<image width=\"84\" height=\"',\n161 │ ┃                     (N * PENDING_FRAMES).toString(),\n    ‡ ┃\n165 │ ┃                     'dur=\"1.3s\" calcMode=\"discrete\" repeatCount=\"indefinite\"/></image></svg>'\n166 │ ┃                 )\n    │ ┗━━━━━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:208:35\n    │\n208 │                 bytes1 v = bytes1(uint8(l * 3 / 5));\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:42\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:62\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                              ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:253:74\n    │\n253 │         if (ch > 2 || face >= FACES || coat > 2 || shirt > 5 || hat > 2) revert Missing();\n    │                                                                          ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:350:31\n    │\n350 │         if (i >= Art.ENTRIES) revert Missing();\n    │                               ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:356:72\n    │\n356 │         if (p.codehash != bytes32(_hex(Art.CHUNK_HASHES, c * 64, 64))) revert BadArt();\n    │                                                                        ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:288:25\n    │\n288 │                         revert(28, 4)\n    │                         ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:56\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                        ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:76\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:68\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                                                    ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:33\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                 ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:239:23\n    │\n239 │             _fren(cv, uint24(combo));\n    │                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:76:30\n   │\n76 │             address holder = identity.ownerOf(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:77:78\n   │\n77 │             if (holder != msg.sender && (holder != strategy || msg.sender != ISeatStrategyMin(strategy).seatOperator()))\n   │                                                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:261:22\n    │\n261 │         int256 dx = -int256(CX);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:262:22\n    │\n262 │         int256 dy = -int256(CY);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:79:17\n   │\n79 │                 revert NotYours(id);\n   │                 ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:81:30\n   │\n81 │             if (claimed[id]) revert AlreadyClaimed(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:321:56\n    │\n321 │         for (uint256 i; i < n; ++i) b[at + i] = bytes1(uint8(v >> (8 * i)));\n    │                                                        ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:105:9\n    │\n105 │         emit WlRootSet(root);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/frens/WorkerFrensRenderer.sol:370:49\n    │\n370 │             extcodecopy(p, add(raw, 32), add(1, mul(first, 33)), mul(frames, 33))\n    │                                                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:184:35\n    │\n184 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:23\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:31\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                               ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:194:13\n    │\n194 │             poolManager.settle{value: pay}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:198:13\n    │\n198 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:23\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:31\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:221:35\n    │\n221 │                 amountSpecified: -int256(ethIn),\n    │                                   ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:27\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:35\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:26\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:34\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:234:35\n    │\n234 │                 amountSpecified: -int256(imdGot),\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:27\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:35\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:23\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:31\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:243:9\n    │\n243 │         poolManager.settle{value: ethPaid}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:316:9\n    │\n316 │         address imdPayTo_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:310:9\n    │\n310 │         address owner_,\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:317:9\n    │\n317 │         address keeper_,\n    │         ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:314:9\n    │\n314 │         address permit2_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:318:9\n    │\n318 │         address relayer_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:312:9\n    │\n312 │         address imd6900_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:319:9\n    │\n319 │         address priceTable_\n    │         ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:315:9\n    │\n315 │         address x402Proxy_,\n    │         ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:313:9\n    │\n313 │         address identity_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:311:9\n    │\n311 │         address imd_,\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:333:9\n    │\n333 │         emit ICreatorToken.TransferValidatorUpdated(address(0), DEFAULT_TRANSFER_VALIDATOR);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:370:16\n    │\n370 │         return uint8(table >> (8 * trait));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:375:16\n    │\n375 │         return uint8((uint256(combo) >> _shift(trait)) & ((1 << _bits(trait)) - 1));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:384:38\n    │\n384 │             if (minTiers[v] > TIERS) revert BadTraits();\n    │                                      ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:406:13\n    │\n406 │             chars += _rules[v].cap;\n    │             ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:369:30\n    │\n369 │         if (trait >= TRAITS) revert BadTraits();\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-no-eth]: external call can be reentered before `floorImd` is updated\n    ╭▸ src/frens/IMD6900Frens.sol:843:13\n    │\n843 │         try IFrenSwapper(swapper).imdToImd6900{gas: FLOOR_BUY_GAS}(imdIn, minOut, address(this)) {}\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:135:9\n    │\n135 │         emit PublicOpened(workerMinted);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:974:9\n    │\n974 │         ITransferValidator721(validator).validateTransfer(msg.sender, from, to, id);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:568:9\n    │\n568 │         emit FrensMinted(requestId, minter, first, count, tier, paid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:853:9\n    │\n853 │         emit FloorBought(spent, 0, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:968:13\n    │\n968 │             revert LastFrenOut();\n    │             ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:564:82\n    │\n564 │         requests[requestId] = Request(minter, tier, lowTier, false, count, 0, 1, uint32(first), 0, 0);\n    │                                                                                  ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:630:38\n    │\n630 │             h = keccak256(abi.encode(h, combos[i]));\n    │                                      ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:622:13\n    │\n622 │         if (block.timestamp > deadline) revert BadVoucher();\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/frens/IMD6900Frens.sol:650:13\n    │\n650 │             emit Revealed(requestId, tokenId, combo, jobId, outputHash);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:639:28\n    │\n639 │             if (code != 0) revert BadCombo(code);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:641:90\n    │\n641 │             if (valueOf(combo, CHARACTER) == 0 && pepe.minted + openLowTier >= pepe.cap) revert BadCombo(3);\n    │                                                                                          ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:634:22\n    │\n634 │         r.revealed = uint8(upTo);\n    │                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:672:53\n    │\n672 │         if (!r.jobApproved || _spent(r.jobNonce) || block.timestamp <= r.jobDeadline) return false;\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/frens/IMD6900Frens.sol:696:9\n    │\n696 │         emit JobPaid(requestId, msg.sender);\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/frens/IMD6900Frens.sol:722:13\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:722:53\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:738:9\n    │\n738 │         emit JobApproved(requestId, nonce, deadline, permitDigest, quoteDigest);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:729:25\n    │\n729 │         r.jobDeadline = uint40(deadline);\n    │                         ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:889:9\n    │\n889 │         emit FloorBought(imdIn, ethIn, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:882:9\n    │\n882 │         emit WethUnwrapped(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:904:9\n    │\n904 │         emit Recycled(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:923:9\n    │\n923 │         emit TreasuryBought(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:944:35\n    │\n944 │     function setTransferValidator(address validator) external onlyCurator {\n    │                                   ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:946:9\n    │\n946 │         emit ICreatorToken.TransferValidatorUpdated(getTransferValidator(), validator);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n     ╭▸ src/frens/IMD6900Frens.sol:1005:26\n     │\n1005 │     function setRenderer(address r) external onlyCurator {\n     │                          ━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1008:9\n     │\n1008 │         emit Setting(\"renderer\", 0, r);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1014:9\n     │\n1014 │         emit Setting(\"artFrozen\", 1, renderer);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1030:17\n     │\n1030 │                 revert BadTraits();\n     │                 ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1042:84\n     │\n1042 │             if (m[i] == 0 || m[i] > MAX_PER_REQUEST || (i > 0 && m[i] < m[i - 1])) revert BadTraits();\n     │                                                                                    ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1058:9\n     │\n1058 │         emit Setting(\"royalty\", bps, address(0));\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/frens/IMD6900Frens.t.sol:527:21\n    │\n527 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n528 │         bytes memory sig = _sign(frens, relayerKey, id, c, d);\n529 │         vm.warp(d + 1);\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/frens/IMD6900Frens.t.sol:537:17\n    │\n537 │         vm.warp(block.timestamp + 30 days);\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/frens/IMD6900Frens.t.sol:682:50\n    │\n682 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:683:17\n    │\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:696:17\n    │\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:718:50\n    │\n718 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:719:17\n    │\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:734:50\n    │\n734 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:278:21\n    │\n278 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:739:17\n    │\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:750:17\n    │\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:757:17\n    │\n757 │         vm.warp(block.timestamp + 601);\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/FrensLaunchAdaptation.t.sol:284:26\n    │\n284 │         uint256 expiry = block.timestamp + 600;\n    │                          ━━━━━━━━━━━━━━━\n    ‡\n291 │         vm.warp(expiry + 1);\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.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:322:17\n    │\n322 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:357:17\n    │\n357 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2791,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/frens/IMD6900FrensValidator.fork.t.sol:IMD6900FrensValidatorForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 282.20µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenSwapper.fork.t.sol:FrenSwapperForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 372.64µs (0.00ns CPU time)\n\nRan 1 test for test/frens/IMD6900FrensPermit2.fork.t.sol:IMD6900FrensPermit2ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 398.32µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenMinter.fork.t.sol:FrenMinterForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 914.93µs (0.00ns CPU time)\n\nRan 1 test for test/FrensPlacement.t.sol:FrensPlacementForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 3.26ms (0.00ns CPU time)\n\nRan 3 tests for test/FrensLaunchReview.t.sol:FrensFactoryReviewTest\n[PASS] test_Audit_ConstructorLeavesTheDocumentedTeamSetup() (gas: 25152531)\n[PASS] test_FreshChainCallsOnlyContractsCreatedByTheLaunch() (gas: 28519889)\n[PASS] test_ProtectedFactoryDeploysBothLaunchesInOrder() (gas: 25351455)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 7.29ms (12.77ms CPU time)\n\nRan 9 tests for test/frens/IMD6900FrensValidator.t.sol:IMD6900FrensValidatorTest\n[PASS] test_AuthorizationDoesNotOutliveTheSale() (gas: 193381)\n[PASS] test_AuthorizationFromTwoTopLevelCallsIsLost() (gas: 86911)\nLogs:\n  sale authorized in a separate top-level call: blocked\n\n[PASS] test_AuthorizationIsForThatFrenOnly() (gas: 394740)\n[PASS] test_AuthorizationIsForThatOperatorOnly() (gas: 479875)\n[PASS] test_ConduitAloneIsBlocked() (gas: 53311)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 79396)\n[PASS] test_HolderSendsItHerself() (gas: 87104)\n[PASS] test_OnlyTheAuthorizerAuthorizes() (gas: 74631)\n[PASS] test_ZoneAuthorizedSaleGoesThroughInOneFrame() (gas: 124660)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.93ms (1.33ms CPU time)\n\nRan 18 tests for test/frens/FrenWorkerGate.t.sol:FrenWorkerGateTest\n[PASS] test_ARefusedMintTakesNothing() (gas: 104998)\n[PASS] test_AnNftCountsOnce_whoeverHoldsItLater() (gas: 151129)\n[PASS] test_CreditsGoWhereTheHolderSays() (gas: 639709)\n[PASS] test_NoGateNoWindow() (gas: 442112)\n[PASS] test_OnlyFrensSpends() (gas: 32613)\n[PASS] test_OnlyTheHolderClaims() (gas: 67063)\n[PASS] test_OpenPublic() (gas: 528980)\n[PASS] test_OwnerMintsBeforeTheOpening() (gas: 2296674)\n[PASS] test_SeatOperatorClaimsTheStrategysNfts() (gas: 138514)\n[PASS] test_TheCreditIsTheMinters() (gas: 576408)\n[PASS] test_WindowClosesAfter420() (gas: 32491871)\n[PASS] test_WindowNeedsACreditPerFren() (gas: 714638)\n[PASS] test_WindowNeverOvershoots() (gas: 36197537)\n[PASS] test_WlAndWorkersShareThe420() (gas: 619421)\n[PASS] test_WlOnlyAsListed_once() (gas: 254028)\n[PASS] test_WlRaisedAmount_claimsTheDifference() (gas: 226858)\n[PASS] test_WlRootOnlyTheOwner_andNoRootNoWl() (gas: 62252)\n[PASS] test_WlWalletsMintInTheWindow_asMuchAsListed() (gas: 751030)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 63.17ms (13.22ms CPU time)\n\nRan 8 tests for test/FrensLaunchReview.t.sol:FrensAuditReviewTest\n[PASS] test_Audit_GovernorCanCloseMintAgain() (gas: 463703)\n[PASS] test_Audit_OwnerOrGovernorCanBlockAllPeerTransfers() (gas: 1372488)\n[PASS] test_Fix_LapsedJobApprovalIsReleasedByTheReveal() (gas: 1034129)\n[PASS] test_Fix_LastFrenOutStaysOutSoFeesHaveAnOwner() (gas: 1171597)\n[PASS] test_Fix_LaunchAdoptsASwapperSeededAtThePoolsPrice() (gas: 1868092)\n[PASS] test_Fix_LaunchRefusesTheWrongArt() (gas: 18811)\n[PASS] test_Fix_LaunchReplacesASwapperSeededOffASkewedPrice() (gas: 2596387)\n[PASS] test_Fix_LaunchsOwnSwapperStartsAtThePoolsPrice() (gas: 236724)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 72.20ms (6.56ms CPU time)\n\nRan 6 tests for test/frens/FrenSwapperAverage.t.sol:FrenSwapperAverageTest\n[PASS] test_AFullBuyMovesItOneStep() (gas: 103298)\n[PASS] test_ASmallBuyMovesItInProportion() (gas: 52688)\n[PASS] test_DustMovesItNextToNothing() (gas: 7581882)\n[PASS] test_FloorRateIsTheLowerReading() (gas: 17216)\n[PASS] test_MovesDownToo() (gas: 52548)\n[PASS] test_OnceABlock() (gas: 91113)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 82.27ms (3.39ms CPU time)\n\nRan 12 tests for test/FrensArtFraming.t.sol:FrensArtFramingTest\n[PASS] testFuzz_EntryPastTheIndexIsMissing(uint256) (runs: 256, μ: 13976, ~: 13943)\nLogs:\n  Bound result 47451646\n\n[PASS] testFuzz_UnknownBackgroundIsMissing(uint256) (runs: 256, μ: 35602, ~: 35592)\nLogs:\n  Bound result 15251\n\n[PASS] test_ATamperedChunkDrawsNothing() (gas: 90530)\n[PASS] test_AttributesDoNotValidate() (gas: 369298)\n[PASS] test_ChunksAreFramedPushData() (gas: 2731712)\n[PASS] test_EveryValueHasAName() (gas: 15679313)\n[PASS] test_IndexHashesAreTheChunks() (gas: 788314)\n[PASS] test_IndexPointsInsideTheChunks() (gas: 525933)\n[PASS] test_LaunchLayersAreWellFormed() (gas: 46186857)\n[PASS] test_NoSuchFrenIsMissing() (gas: 4375022)\n[PASS] test_PalettesFitTogether() (gas: 13989817)\n[PASS] test_WithoutTheSwarmNothingDraws() (gas: 5947823)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 82.36ms (273.33ms CPU time)\n\nRan 19 tests for test/FrensLaunchFailures.t.sol:FrensLaunchFailuresTest\n[PASS] testFuzz_CheckKnowsAFren(uint24,uint8) (runs: 512, μ: 1938360, ~: 1908067)\nLogs:\n  Bound result 0\n\n[PASS] testFuzz_OtherSaltsLandElsewhere(bytes32) (runs: 256, μ: 34945, ~: 34945)\n[PASS] testFuzz_QuoteIsTheCurveBeforeAnyoneHolds(uint256) (runs: 512, μ: 324453, ~: 322366)\nLogs:\n  Bound result 42\n\n[PASS] test_AnotherOwnerMovesTheFrens() (gas: 34214)\n[PASS] test_CollectionRefusesAShortPriceTable() (gas: 3059925)\n[PASS] test_CollectionWaitsForTheTeamWallet() (gas: 5941920)\n[PASS] test_EveryPriceIsTheTables() (gas: 27629816)\n[PASS] test_FreshChainLaunchesAreIndependent() (gas: 1351085)\n[PASS] test_LaunchContractsTakeNoEth() (gas: 687657)\n[PASS] test_ModulesFailOverCorruptedFrens() (gas: 97101)\n[PASS] test_ModulesNeedTheCollection() (gas: 106270)\n[PASS] test_NoLaunchActorHoldsARole() (gas: 4583570)\n[PASS] test_NoPricePastTheTable() (gas: 41307)\n[PASS] test_OtherSaltSchemeFailsTheLaunch() (gas: 140656)\n[PASS] test_RefusingDeployerFailsTheLaunch() (gas: 103369)\n[PASS] test_ScriptRefusesAnUnplacedChain() (gas: 14619610)\n[PASS] test_SecondLaunchReusesWhatIsPlaced() (gas: 1313470)\n[PASS] test_SilentDeployerFailsTheLaunch() (gas: 93234)\n[PASS] test_WrongArtFailsTheArtLaunch() (gas: 588910)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 82.36ms (222.74ms CPU time)\n\nRan 84 tests for test/frens/IMD6900Frens.t.sol:IMD6900FrensTest\n[PASS] test_AMintNeverFailsOnTheSwap() (gas: 946501)\n[PASS] test_BatchCannotRepeatAFren() (gas: 641777)\n[PASS] test_BatchCountIsOneTo69() (gas: 2185636)\n[PASS] test_BatchCountsTheCapsAsItGoes() (gas: 11785283)\n[PASS] test_BatchHoldsPepesForLowTiers() (gas: 12180874)\n[PASS] test_BatchPaysOneJob() (gas: 700293)\n[PASS] test_BatchRevealIsExactlyTheVoucher() (gas: 1012095)\n[PASS] test_BatchRevealsThemAll() (gas: 2254191)\n[PASS] test_BatchSoldOut() (gas: 90765188)\n[PASS] test_BuyTreasuryRespectsMaxPay() (gas: 1807199)\n[PASS] test_BuyingFromTheTreasuryRaisesTheFloor() (gas: 3060520)\n[PASS] test_CurveWhileAboveTheFloor() (gas: 1591127)\n[PASS] test_DearerImd6900RaisesTheFloorPrice() (gas: 2846975)\n[PASS] test_DustEthBuyCannotTakeTheMintsTurn() (gas: 539958)\n[PASS] test_EveryFrenIsOneOfAKind() (gas: 947054)\n[PASS] test_FailedJob_payAnother() (gas: 1168121)\n[PASS] test_FeeEthBuysTheFloor() (gas: 1702668)\n[PASS] test_FloorBuysAreAnyones_onePerBlock() (gas: 1886993)\n[PASS] test_FloorBuysAreCapped() (gas: 55657779)\n[PASS] test_FloorIsReserveOverFrensOut() (gas: 2749300)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 2110486)\n[PASS] test_FrenSentStraightToTheTreasuryCounts() (gas: 2605187)\n[PASS] test_IsACreatorToken() (gas: 41743)\n[PASS] test_JobPaymentIsPinnedToImdsPayee() (gas: 974626)\n[PASS] test_KeeperApprovesTheJobPaidFor() (gas: 653212)\n[PASS] test_KeeperCannotApproveLongLivedPayments() (gas: 434010)\n[PASS] test_LapsedPaymentIsUsedAgain() (gas: 715229)\n[PASS] test_LastFrenOutStaysOut() (gas: 1582490)\n[PASS] test_LaterPartsAgreeOnWhatIsRevealed() (gas: 1347460)\n[PASS] test_LowTierRequestsNeedAPepeLeft() (gas: 11712756)\n[PASS] test_MaxMintGrowsWithTheBag() (gas: 4682103)\n[PASS] test_MaxPayStopsAPriceThatMoved() (gas: 10066140)\n[PASS] test_MaxRequest() (gas: 9919282)\n[PASS] test_MinTierOnlyGoesDown() (gas: 138825)\n[PASS] test_MintMintsAtOnce_unrevealed() (gas: 770570)\n[PASS] test_MintNeedsSealedTraits() (gas: 8627562)\n[PASS] test_MintNeverBelowTheFloor() (gas: 3403449)\n[PASS] test_MintNeverSkipsTheBuyForWantOfGas() (gas: 708859)\n[PASS] test_NoMintIntoTheTreasury() (gas: 1590016)\n[PASS] test_NoRequestForNobody() (gas: 61934)\n[PASS] test_NoTierInsideAFlashLoan() (gas: 687603)\n[PASS] test_NobodyElseApprovesJobs() (gas: 435602)\n[PASS] test_OneRevealPerFren() (gas: 717815)\n[PASS] test_OnlyHoldersRecycle() (gas: 1587638)\n[PASS] test_OnlyRealFrens() (gas: 743528)\n[PASS] test_OnlyTheOwnerSetsTheValidator() (gas: 1060220)\n[PASS] test_OwnerAdjustsTheArtUntilFrozen() (gas: 1552272)\n[PASS] test_PairRules() (gas: 875609)\n[PASS] test_PartsOnlyGoForward() (gas: 1035574)\n[PASS] test_PriceRisesWithMints() (gas: 38121727)\n[PASS] test_PriceTableIsTheCurve() (gas: 37031661)\n[PASS] test_QuoteCountsUnsweptImd() (gas: 3192315)\n[PASS] test_RareTraitsNeedTheTier() (gas: 1366007)\n[PASS] test_RecyclePaysTheFloorAndKeepsIt() (gas: 2892194)\n[PASS] test_RecyclePaysTheWaitingImdToo() (gas: 2719969)\n[PASS] test_ReleaseLapsedJobAfterTheReveal() (gas: 2133093)\n[PASS] test_RequestMintFor() (gas: 1084049)\n[PASS] test_ReserveOnlyLeavesThroughRecycle() (gas: 2826030)\n[PASS] test_RevealFollowsTheFren() (gas: 749295)\n[PASS] test_RevealInParts() (gas: 9744736)\nLogs:\n  gas, minting 69: 2117876\n  gas, revealing 30: 3092889\n\n[PASS] test_RevealNeedsTheRelayersVoucher() (gas: 803728)\n[PASS] test_RevealReleasesALapsedPayment() (gas: 916235)\n[PASS] test_RevealShowsTheFren() (gas: 1189238)\n[PASS] test_RevealsWhenever() (gas: 661569)\n[PASS] test_RoyaltiesPayTheFloor() (gas: 1718930)\n[PASS] test_RoyaltyBpsIsCappedAndOwnerOnly() (gas: 109478)\n[PASS] test_RulesAreFrozenOnceSealed() (gas: 69602)\n[PASS] test_SealNeedsEveryTraitAndTheSupply() (gas: 10522996)\n[PASS] test_SetMaxMint() (gas: 178348)\n[PASS] test_ShortSwapReverts() (gas: 1043815)\n[PASS] test_SwapperMayPullOnlyTheBuy() (gas: 954501)\n[PASS] test_TheOwnerKeepsTheCollection_theGovernorTheMechanics() (gas: 488972)\n[PASS] test_TierCountsWhatIsLeftAfterPaying() (gas: 919870)\n[PASS] test_TierIsTheBestOfTheBag() (gas: 633637)\n[PASS] test_TierIsTheMints() (gas: 1350662)\n[PASS] test_TierThresholdsOwnerOnlyAndOrdered() (gas: 155016)\n[PASS] test_TradesGoPastTheValidator() (gas: 1343397)\n[PASS] test_TraitCapsHold() (gas: 25774957)\n[PASS] test_TreasuryRoundTripCostsTheFloor() (gas: 3239545)\n[PASS] test_UnneededRetryFeedsTheFloor() (gas: 1023226)\n[PASS] test_UnrevealedFrenSellsAtTheFloor() (gas: 1015894)\n[PASS] test_UnrevealedFrensShareTheFloor() (gas: 1451826)\n[PASS] test_UnusedJobFeedsTheFloor() (gas: 697059)\n[PASS] test_VouchersExpire() (gas: 472525)\nSuite result: ok. 84 passed; 0 failed; 0 skipped; finished in 82.33ms (181.65ms CPU time)\n\nRan 15 tests for test/FrensLaunchAdaptation.t.sol:FrensLaunchAdaptationTest\n[PASS] test_BootstrapThenOpenAndResume() (gas: 2316354)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\n[PASS] test_FirstFrensBeforeFeesPreventsPublicFloorCapture() (gas: 3361837)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_FirstFrensRequiresPausedBuysAndNonzeroCount() (gas: 1786016)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRefuseAnOwnerlessFloor() (gas: 1828998)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRejectChangedWiring() (gas: 2613667)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_PlacedRejectsMissingOrWrongModules() (gas: 126553)\n[PASS] test_PlacedRejectsWrongCollectionAndForeignDependencies() (gas: 65517)\n[PASS] test_PlacedReportsTheLaunchModules() (gas: 51662)\n[PASS] test_ResumeRequiresDistributorAndActualSwapperExemption() (gas: 2165092)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_FirstMintCapturesExistingFloorIfRunbookIsBypassed() (gas: 2980745)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_LaunchBlockPushSeedsSwapperAndSetupRejectsAfterPriceReturns() (gas: 2428184)\n[PASS] test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection() (gas: 2351055)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_UnsupportedRoyaltyTokenHasNoFloorRoute() (gas: 3703404)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupPausesBeforeFirstMintEvenWhenAlreadyDistributor() (gas: 1815843)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupUsesTheLaunchsReplacementSwapper() (gas: 6693994)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 110.15ms (12.11ms CPU time)\n\nRan 13 tests for test/FrensPlacement.t.sol:FrensPlacementTest\n[PASS] test_Attributes() (gas: 19594305)\n[PASS] test_CodeIsWhatTheSourcesBuild() (gas: 88334)\n[PASS] test_DeploysOnAFreshChain() (gas: 51532495)\n[PASS] test_EachLaunchFitsOneTransaction() (gas: 105289625)\nLogs:\n  the collection launch: gas, all in: 12308310\n  the art launch: gas, all in: 13605058\n\n[PASS] test_LandsWhereThePlanSays() (gas: 51643509)\n[PASS] test_NewArtReadsBackExactly() (gas: 14173584)\n[PASS] test_PassesTheAdmissionScan() (gas: 87697392)\n[PASS] test_PlanFollowsFromTheCode() (gas: 79539)\n[PASS] test_PlanMatchesTheScript() (gas: 14147485)\n[PASS] test_PriceTableIsTheCurve() (gas: 52824571)\n[PASS] test_RefusesOtherArt() (gas: 12971045)\n[PASS] test_SameAddressesWhoeverRunsIt() (gas: 102213411)\n[PASS] test_TakesWhatSomeonePlacedFirst() (gas: 52211127)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 110.11ms (245.07ms CPU time)\n\nRan 1 test for test/FrensSplitGas.t.sol:FrensSplitGasTest\n[PASS] test_CollectionFitsButLegacyFourContractBundleExceedsCap() (gas: 100924258)\nLogs:\n  legacy four-contract launch: 23723070\n  two-contract collection launch: 13340309\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 111.65ms (111.54ms CPU time)\n\nRan 1 test for test/FrensPlacedInvariants.t.sol:FrensPlacedInvariantsTest\n[PASS]\nFrensPlacedInvariantsTest invariants:\n[PASS] invariant_CapsHold\n[PASS] invariant_FloorIsAShareOfTheWorld\n[PASS] invariant_FrensAreConserved\n[PASS] invariant_ImdIsTheBooks\n[PASS] invariant_ReserveIsExactlyWhatIsHere\n FrensPlacedInvariantsTest invariants (runs: 32, calls: 1920, reverts: 10)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| PlacedFrensHandler | approveJob      | 125   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyFloor        | 127   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyTreasury     | 124   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donate6900      | 105   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donateImd       | 115   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | feeEth          | 125   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mint            | 258   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mintThenRecycle | 121   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | recycle         | 114   | 10      | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | retryJob        | 123   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | reveal          | 134   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setParams       | 113   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setRate         | 117   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | transfer        | 110   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | warp            | 109   | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000\n  Bound result 57\n  Bound result 3\n  Bound result 192\n  Bound result 1\n  Bound result 37\n  Bound result 2\n  Bound result 0\n  Bound result 818339788362339122047511\n  Bound result 997944142186410158031\n  Bound result 2116\n  Bound result 56\n  Bound result 1\n  Bound result 40\n  Bound result 5000\n  Bound result 0\n  Bound result 157561922826911960461\n  Bound result 190939387264707843\n  Bound result 48\n  Bound result 144\n  Bound result 36\n  Bound result 1\n  Bound result 147083082001620885736\n  Bound result 148893962131730346\n  Bound result 653945\n  Bound result 1\n  Bound result 50000\n  Bound result 3\n  Bound result 7864320\n  Bound result 8134\n  Bound result 2\n  Bound result 1662\n  Bound result 70\n  Bound result 1\n  Bound result 398\n  Bound result 44\n  Bound result 6144\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 1109\n  Bound result 2\n  Bound result 64\n  Bound result 36\n  Bound result 1\n  Bound result 1126\n  Bound result 99\n  Bound result 3\n  Bound result 88837921757628067770\n  Bound result 13908443696640\n  Bound result 250000000000000000\n  Bound result 2\n  Bound result 8\n  Bound result 8\n  Bound result 36\n  Bound result 1000000\n  Bound result 3200\n  Bound result 0\n  Bound result 11\n  Bound result 4\n  Bound result 51\n  Bound result 42\n  Bound result 0\n  Bound result 63976089155071176925531\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.69s (2.68s CPU time)\n\nRan 17 test suites in 2.69s (3.50s CPU time): 189 tests passed, 0 failed, 5 skipped (194 total tests)\n","passed":true},{"durationMs":97,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrenMinter.buyImd(uint256)\",\"FrenMinter.mintWithEth(uint8,uint256)\",\"FrenMinter.quoteEth(uint8)\",\"FrenMinter.quoteFloor(uint256)\",\"FrenMinter.quoteFloorEth(uint256)\",\"FrenMinter.unlockCallback(bytes)\",\"FrenSwapper.ethToImd6900(uint256,address)\",\"FrenSwapper.imdToImd6900(uint256,uint256,address)\",\"FrenSwapper.unlockCallback(bytes)\",\"FrenWorkerGate.cancelOwnershipHandover()\",\"FrenWorkerGate.claim(uint256[],address)\",\"FrenWorkerGate.claimWl(uint256,bytes32[],address)\",\"FrenWorkerGate.completeOwnershipHandover(address)\",\"FrenWorkerGate.openPublic()\",\"FrenWorkerGate.renounceOwnership()\",\"FrenWorkerGate.requestOwnershipHandover()\",\"FrenWorkerGate.setWlRoot(bytes32)\",\"FrenWorkerGate.spend(address,uint256)\",\"FrenWorkerGate.transferOwnership(address)\",\"IMD6900Frens.addPairRule((uint8,uint8,uint8,uint8,uint8))\",\"IMD6900Frens.approve(address,uint256)\",\"IMD6900Frens.approveJob(uint256,uint256,uint256,(string,bytes32,string,bytes32,bytes32,string,uint256))\",\"IMD6900Frens.buyFloor(uint256)\",\"IMD6900Frens.buyFloorWithEth(uint256,uint256)\",\"IMD6900Frens.buyTreasury(uint256,uint256,uint256)\",\"IMD6900Frens.cancelOwnershipHandover()\",\"IMD6900Frens.completeOwnershipHandover(address)\",\"IMD6900Frens.freezeArt()\",\"IMD6900Frens.lowerMinTier(uint8,uint8,uint8)\",\"IMD6900Frens.receive()\",\"IMD6900Frens.recycle(uint256)\",\"IMD6900Frens.releaseLapsedJob(uint256)\",\"IMD6900Frens.renounceOwnership()\",\"IMD6900Frens.requestMint(uint8,uint256)\",\"IMD6900Frens.requestMintFor(address,uint8,uint256)\",\"IMD6900Frens.requestOwnershipHandover()\",\"IMD6900Frens.retryJob(uint256)\",\"IMD6900Frens.reveal(uint256,uint24[],string,bytes32,uint256,bytes,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256,bytes)\",\"IMD6900Frens.sealTraits()\",\"IMD6900Frens.setApprovalForAll(address,bool)\",\"IMD6900Frens.setGovernor(address)\",\"IMD6900Frens.setMaxMint(uint8[4])\",\"IMD6900Frens.setMintOpen(bool)\",\"IMD6900Frens.setModules(address,address)\",\"IMD6900Frens.setParams(uint256,uint256,uint256)\",\"IMD6900Frens.setRenderer(address)\",\"IMD6900Frens.setRoles(address,address,address)\",\"IMD6900Frens.setRoyalty(uint96)\",\"IMD6900Frens.setTiers(uint256[3],uint256[3],uint256[3])\",\"IMD6900Frens.setTraitRules(uint8,uint16[],uint8[])\",\"IMD6900Frens.setTransferValidator(address)\",\"IMD6900Frens.transferFrom(address,address,uint256)\",\"IMD6900Frens.transferOwnership(address)\",\"IMD6900Frens.unwrapWeth()\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":145,\"README.md\":181,\"foundry.toml\":22,\"launch.json\":16,\"script/art/chunks.py\":165,\"script/art/data/bgpal/cleanlab_blue.bin\":1,\"script/art/data/bgpal/cleanlab_green.bin\":1,\"script/art/data/bgpal/cleanlab_red.bin\":1,\"script/art/data/bgpal/messylab_blue.bin\":1,\"script/art/data/bgpal/messylab_green.bin\":1,\"script/art/data/bgpal/messylab_red.bin\":1,\"script/art/data/bgpal/tubeblue.bin\":1,\"script/art/data/bgpal/tubegreen.bin\":2,\"script/art/data/bgpal/tubered.bin\":1,\"script/art/data/bgpal/tubeyellow.bin\":1,\"script/art/data/bgpal/wireframe_green.bin\":2,\"script/art/data/bgpal/wireframe_red.bin\":1,\"script/art/data/expected.json\":53,\"script/art/data/layers/bg0.bin\":9,\"script/art/data/layers/bg1.bin\":9,\"script/art/data/layers/bg10.bin\":32,\"script/art/data/layers/bg11.bin\":32,\"script/art/data/layers/bg2.bin\":9,\"script/art/data/layers/bg3.bin\":15,\"script/art/data/layers/bg4.bin\":15,\"script/art/data/layers/bg5.bin\":15,\"script/art/data/layers/bg6.bin\":26,\"script/art/data/layers/bg7.bin\":26,\"script/art/data/layers/bg8.bin\":26,\"script/art/data/layers/bg9.bin\":26,\"script/art/data/layers/coat.bin\":20,\"script/art/data/layers/item5.bin\":2,\"script/art/data/manifest.json\":500,\"script/art/data/shared.bin\":4,\"script/art/data/tables.bin\":2,\"script/art/swarm-v2.json\":374,\"script/frens/DeployFrens.s.sol\":243,\"script/frens/FrensTimelockBatch.s.sol\":105,\"script/frens/price/prices-swarm.bin\":6,\"script/frens/price/prices.bin\":6,\"script/frens/price/prices.json\":26,\"script/frens/price/prices.py\":55,\"script/frens/price/prices_sol.py\":33,\"script/placement/addresses.json\":7,\"script/placement/gen.py\":135,\"src/FrensCode.sol\":15,\"src/FrensPlacement.sol\":129,\"src/FrensPlan.sol\":37,\"src/art/WorkerArt.sol\":671,\"src/art/WorkerArtIndex.sol\":56,\"src/frens/FrenMinter.sol\":194,\"src/frens/FrenPrices.sol\":122,\"src/frens/FrenSwapper.sol\":249,\"src/frens/FrenWorkerGate.sol\":137,\"src/frens/IMD6900Frens.sol\":1072,\"src/frens/WorkerFrensRenderer.sol\":377,\"test/FrensArtFraming.t.sol\":290,\"test/FrensLaunchAdaptation.t.sol\":317,\"test/FrensLaunchFailures.t.sol\":526,\"test/FrensLaunchReview.t.sol\":449,\"test/FrensPlacedInvariants.t.sol\":619,\"test/FrensPlacement.t.sol\":818,\"test/FrensSplitGas.t.sol\":68,\"test/frens/DataWriter.sol\":21,\"test/frens/FrenMinter.fork.t.sol\":189,\"test/frens/FrenSwapper.fork.t.sol\":134,\"test/frens/FrenSwapperAverage.t.sol\":77,\"test/frens/FrenWorkerGate.t.sol\":350,\"test/frens/FrensRules.sol\":75,\"test/frens/IMD6900Frens.t.sol\":1710,\"test/frens/IMD6900FrensPermit2.fork.t.sol\":130,\"test/frens/IMD6900FrensValidator.fork.t.sol\":146,\"test/frens/IMD6900FrensValidator.t.sol\":231,\"test/test_collection_manifest.py\":28},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"11224a2afbf0a0b8e7087439160b0bc60c92784a52e3a1169180295347d9cfaf","verifiedTreeHash":"4733a195f015d26fd2ab4945673f28f89592c97b","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":154812,"exitCode":0,"name":"build","output":"Compiling 96 files with Solc 0.8.30\nSolc 0.8.30 finished in 154.61s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n   --> test/FrensPlacement.t.sol:786:9:\n    |\n786 |         uint256 s = _find(j, key, 0) + key.length;\n    |         ^^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/FrensPlacement.t.sol:477:5:\n    |\n477 |     DeployFrens s;\n    |     ^^^^^^^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to view\n   --> test/FrensLaunchAdaptation.t.sol:184:5:\n    |\n184 |     function test_PlacedReportsTheLaunchModules() public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/frens/IMD6900Frens.sol:953:66\n    │\n953 │         return (ITransferValidator721.validateTransfer.selector, true);\n    │                                                                  ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:68\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                                                    ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:33\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                 ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:69:9\n   │\n69 │         address frens_,\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:71:9\n   │\n71 │         address imdPoolHook_\n   │         ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:76:30\n   │\n76 │             address holder = identity.ownerOf(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:77:78\n   │\n77 │             if (holder != msg.sender && (holder != strategy || msg.sender != ISeatStrategyMin(strategy).seatOperator()))\n   │                                                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:68:9\n   │\n68 │         address imd6900_,\n   │         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:70:9\n   │\n70 │         address pairHook_,\n   │         ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:67:9\n   │\n67 │         address imd_,\n   │         ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:79:17\n   │\n79 │                 revert NotYours(id);\n   │                 ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:81:30\n   │\n81 │             if (claimed[id]) revert AlreadyClaimed(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:105:9\n    │\n105 │         emit WlRootSet(root);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `keeper` is changed without an event but is used for access control\n    ╭▸ src/frens/IMD6900Frens.sol:998:36\n    │\n998 │         if (keeper_ != address(0)) keeper = keeper_;\n    │                                    ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `governor` is changed without an event but is used for access control\n     ╭▸ src/frens/IMD6900Frens.sol:1064:9\n     │\n1064 │         governor = g;\n     │         ━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:150:16\n    │\n150 │         return uint160(zeroForOne ? uint256(p) * 1e18 / f : uint256(p) * f / 1e18);\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:54:13\n   │\n54 │ ┏             abi.encodePacked(\n55 │ ┃                 FrensCode.FRENS,\n56 │ ┃                 abi.encode(\n57 │ ┃                     FrensPlan.OWNER,\n   ‡ ┃\n68 │ ┃             )\n   │ ┗━━━━━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:93:29\n   │\n93 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n94 │ ┃             FrensCode.SWAPPER,\n95 │ ┃             abi.encode(\n96 │ ┃                 FrensPlan.POOL_MANAGER, FrensPlan.IMD, FrensPlan.IMD6900, f, FrensPlan.PAIR_HOOK, FrensPlan.POOL4_HOOK\n97 │ ┃             )\n98 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:109:13\n    │\n109 │ ┏             abi.encodePacked(\n110 │ ┃                 FrensCode.MINTER, abi.encode(FrensPlan.POOL_MANAGER, f, FrensPlan.POOL4_HOOK, FrensPlan.PAIR_HOOK)\n111 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:115:13\n    │\n115 │             abi.encodePacked(FrensCode.GATE, abi.encode(FrensPlan.OWNER, f, FrensPlan.IDENTITY, FrensPlan.IMD6900))\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:184:35\n    │\n184 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:23\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:31\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                               ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:194:13\n    │\n194 │             poolManager.settle{value: pay}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:198:13\n    │\n198 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:23\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:31\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:221:35\n    │\n221 │                 amountSpecified: -int256(ethIn),\n    │                                   ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:27\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:35\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:26\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:34\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:234:35\n    │\n234 │                 amountSpecified: -int256(imdGot),\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:27\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:35\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:23\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:31\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:243:9\n    │\n243 │         poolManager.settle{value: ethPaid}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:135:9\n    │\n135 │         emit PublicOpened(workerMinted);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:59:29\n   │\n59 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n60 │ ┃             '{\"name\":\"Worker Fren #',\n61 │ ┃             tokenId.toString(),\n62 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n68 │ ┃             \"}\"\n69 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:75:29\n   │\n75 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n76 │ ┃             '{\"name\":\"Worker Fren #',\n77 │ ┃             tokenId.toString(),\n78 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n82 │ ┃             '\",\"attributes\":[{\"trait_type\":\"Status\",\"value\":\"Unrevealed\"}]}'\n83 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:124:21\n    │\n124 │                 if (k == i) {\n    │                     ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:125:54\n    │\n125 │                     bytes memory out = new bytes(j - start);\n    │                                                      ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:39\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                       ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:55\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/frens/WorkerFrensRenderer.sol:158:17\n    │\n158 │ ┏                 abi.encodePacked(\n159 │ ┃                     '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 84 84\" width=\"840\" height=\"840\">',\n160 │ ┃                     '<image width=\"84\" height=\"',\n161 │ ┃                     (N * PENDING_FRAMES).toString(),\n    ‡ ┃\n165 │ ┃                     'dur=\"1.3s\" calcMode=\"discrete\" repeatCount=\"indefinite\"/></image></svg>'\n166 │ ┃                 )\n    │ ┗━━━━━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:77\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                                             ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:208:35\n    │\n208 │                 bytes1 v = bytes1(uint8(l * 3 / 5));\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:42\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:62\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                              ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:253:74\n    │\n253 │         if (ch > 2 || face >= FACES || coat > 2 || shirt > 5 || hat > 2) revert Missing();\n    │                                                                          ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:350:31\n    │\n350 │         if (i >= Art.ENTRIES) revert Missing();\n    │                               ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:356:72\n    │\n356 │         if (p.codehash != bytes32(_hex(Art.CHUNK_HASHES, c * 64, 64))) revert BadArt();\n    │                                                                        ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:288:25\n    │\n288 │                         revert(28, 4)\n    │                         ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:56\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                        ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:76\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:239:23\n    │\n239 │             _fren(cv, uint24(combo));\n    │                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:261:22\n    │\n261 │         int256 dx = -int256(CX);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:262:22\n    │\n262 │         int256 dy = -int256(CY);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:321:56\n    │\n321 │         for (uint256 i; i < n; ++i) b[at + i] = bytes1(uint8(v >> (8 * i)));\n    │                                                        ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/frens/WorkerFrensRenderer.sol:370:49\n    │\n370 │             extcodecopy(p, add(raw, 32), add(1, mul(first, 33)), mul(frames, 33))\n    │                                                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:118:35\n    │\n118 │             if (r.length == 68 && bytes4(r) == Quoted.selector) {\n    │                                   ━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:138:79\n    │\n138 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                 ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:25\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:33\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:148:39\n    │\n148 │                     amountSpecified: -int256(imdIn),\n    │                                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:35\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:43\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:158:70\n    │\n158 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: int256(imdOut), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:25\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:33\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:23\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:31\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenMinter.sol:165:9\n    │\n165 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/FrenMinter.sol:183:29\n    │\n183 │             if (!withEth && cost > a) continue;\n    │                             ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:178:17\n    │\n178 │             try frens.quote(n) returns (uint256 q) {\n    │                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:22\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:51\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:84\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:184:22\n    │\n184 │             if (n <= frens.maxMint(_tier(withEth ? a : a - cost, b, c))) return n;\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:312:9\n    │\n312 │         address imd6900_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:317:9\n    │\n317 │         address keeper_,\n    │         ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:310:9\n    │\n310 │         address owner_,\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:318:9\n    │\n318 │         address relayer_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:313:9\n    │\n313 │         address identity_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:316:9\n    │\n316 │         address imdPayTo_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:319:9\n    │\n319 │         address priceTable_\n    │         ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:311:9\n    │\n311 │         address imd_,\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:314:9\n    │\n314 │         address permit2_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:315:9\n    │\n315 │         address x402Proxy_,\n    │         ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:333:9\n    │\n333 │         emit ICreatorToken.TransferValidatorUpdated(address(0), DEFAULT_TRANSFER_VALIDATOR);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:370:16\n    │\n370 │         return uint8(table >> (8 * trait));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:375:16\n    │\n375 │         return uint8((uint256(combo) >> _shift(trait)) & ((1 << _bits(trait)) - 1));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:384:38\n    │\n384 │             if (minTiers[v] > TIERS) revert BadTraits();\n    │                                      ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:406:13\n    │\n406 │             chars += _rules[v].cap;\n    │             ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:369:30\n    │\n369 │         if (trait >= TRAITS) revert BadTraits();\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-no-eth]: external call can be reentered before `floorImd` is updated\n    ╭▸ src/frens/IMD6900Frens.sol:843:13\n    │\n843 │         try IFrenSwapper(swapper).imdToImd6900{gas: FLOOR_BUY_GAS}(imdIn, minOut, address(this)) {}\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:974:9\n    │\n974 │         ITransferValidator721(validator).validateTransfer(msg.sender, from, to, id);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:568:9\n    │\n568 │         emit FrensMinted(requestId, minter, first, count, tier, paid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:853:9\n    │\n853 │         emit FloorBought(spent, 0, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:968:13\n    │\n968 │             revert LastFrenOut();\n    │             ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:564:82\n    │\n564 │         requests[requestId] = Request(minter, tier, lowTier, false, count, 0, 1, uint32(first), 0, 0);\n    │                                                                                  ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:630:38\n    │\n630 │             h = keccak256(abi.encode(h, combos[i]));\n    │                                      ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:622:13\n    │\n622 │         if (block.timestamp > deadline) revert BadVoucher();\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/frens/IMD6900Frens.sol:650:13\n    │\n650 │             emit Revealed(requestId, tokenId, combo, jobId, outputHash);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:639:28\n    │\n639 │             if (code != 0) revert BadCombo(code);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:641:90\n    │\n641 │             if (valueOf(combo, CHARACTER) == 0 && pepe.minted + openLowTier >= pepe.cap) revert BadCombo(3);\n    │                                                                                          ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:634:22\n    │\n634 │         r.revealed = uint8(upTo);\n    │                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:672:53\n    │\n672 │         if (!r.jobApproved || _spent(r.jobNonce) || block.timestamp <= r.jobDeadline) return false;\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/frens/IMD6900Frens.sol:696:9\n    │\n696 │         emit JobPaid(requestId, msg.sender);\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/frens/IMD6900Frens.sol:722:13\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:722:53\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:738:9\n    │\n738 │         emit JobApproved(requestId, nonce, deadline, permitDigest, quoteDigest);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:729:25\n    │\n729 │         r.jobDeadline = uint40(deadline);\n    │                         ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:889:9\n    │\n889 │         emit FloorBought(imdIn, ethIn, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:882:9\n    │\n882 │         emit WethUnwrapped(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:904:9\n    │\n904 │         emit Recycled(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:923:9\n    │\n923 │         emit TreasuryBought(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:944:35\n    │\n944 │     function setTransferValidator(address validator) external onlyCurator {\n    │                                   ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:946:9\n    │\n946 │         emit ICreatorToken.TransferValidatorUpdated(getTransferValidator(), validator);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n     ╭▸ src/frens/IMD6900Frens.sol:1005:26\n     │\n1005 │     function setRenderer(address r) external onlyCurator {\n     │                          ━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1008:9\n     │\n1008 │         emit Setting(\"renderer\", 0, r);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1014:9\n     │\n1014 │         emit Setting(\"artFrozen\", 1, renderer);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1030:17\n     │\n1030 │                 revert BadTraits();\n     │                 ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1042:84\n     │\n1042 │             if (m[i] == 0 || m[i] > MAX_PER_REQUEST || (i > 0 && m[i] < m[i - 1])) revert BadTraits();\n     │                                                                                    ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1058:9\n     │\n1058 │         emit Setting(\"royalty\", bps, address(0));\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/frens/IMD6900Frens.t.sol:527:21\n    │\n527 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n528 │         bytes memory sig = _sign(frens, relayerKey, id, c, d);\n529 │         vm.warp(d + 1);\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/frens/IMD6900Frens.t.sol:537:17\n    │\n537 │         vm.warp(block.timestamp + 30 days);\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/frens/IMD6900Frens.t.sol:682:50\n    │\n682 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:683:17\n    │\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:696:17\n    │\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:718:50\n    │\n718 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:719:17\n    │\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:734:50\n    │\n734 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:278:21\n    │\n278 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:739:17\n    │\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:750:17\n    │\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:757:17\n    │\n757 │         vm.warp(block.timestamp + 601);\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/FrensLaunchAdaptation.t.sol:284:26\n    │\n284 │         uint256 expiry = block.timestamp + 600;\n    │                          ━━━━━━━━━━━━━━━\n    ‡\n291 │         vm.warp(expiry + 1);\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.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:322:17\n    │\n322 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:357:17\n    │\n357 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":9287,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/frens/IMD6900FrensValidator.fork.t.sol:IMD6900FrensValidatorForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 260.46µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenSwapper.fork.t.sol:FrenSwapperForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 369.40µs (0.00ns CPU time)\n\nRan 1 test for test/frens/IMD6900FrensPermit2.fork.t.sol:IMD6900FrensPermit2ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 353.83µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenMinter.fork.t.sol:FrenMinterForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 634.94µs (0.00ns CPU time)\n\nRan 1 test for test/FrensPlacement.t.sol:FrensPlacementForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 2.24ms (0.00ns CPU time)\n\nRan 9 tests for test/frens/IMD6900FrensValidator.t.sol:IMD6900FrensValidatorTest\n[PASS] test_AuthorizationDoesNotOutliveTheSale() (gas: 193381)\n[PASS] test_AuthorizationFromTwoTopLevelCallsIsLost() (gas: 86911)\nLogs:\n  sale authorized in a separate top-level call: blocked\n\n[PASS] test_AuthorizationIsForThatFrenOnly() (gas: 394740)\n[PASS] test_AuthorizationIsForThatOperatorOnly() (gas: 479875)\n[PASS] test_ConduitAloneIsBlocked() (gas: 53311)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 79396)\n[PASS] test_HolderSendsItHerself() (gas: 87104)\n[PASS] test_OnlyTheAuthorizerAuthorizes() (gas: 74631)\n[PASS] test_ZoneAuthorizedSaleGoesThroughInOneFrame() (gas: 124660)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 5.54ms (815.40µs CPU time)\n\nRan 15 tests for test/FrensLaunchAdaptation.t.sol:FrensLaunchAdaptationTest\n[PASS] test_BootstrapThenOpenAndResume() (gas: 2316354)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\n[PASS] test_FirstFrensBeforeFeesPreventsPublicFloorCapture() (gas: 3361837)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_FirstFrensRequiresPausedBuysAndNonzeroCount() (gas: 1786016)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRefuseAnOwnerlessFloor() (gas: 1828998)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRejectChangedWiring() (gas: 2613667)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_PlacedRejectsMissingOrWrongModules() (gas: 126553)\n[PASS] test_PlacedRejectsWrongCollectionAndForeignDependencies() (gas: 65517)\n[PASS] test_PlacedReportsTheLaunchModules() (gas: 51662)\n[PASS] test_ResumeRequiresDistributorAndActualSwapperExemption() (gas: 2165092)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_FirstMintCapturesExistingFloorIfRunbookIsBypassed() (gas: 2980745)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_LaunchBlockPushSeedsSwapperAndSetupRejectsAfterPriceReturns() (gas: 2428184)\n[PASS] test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection() (gas: 2351055)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_UnsupportedRoyaltyTokenHasNoFloorRoute() (gas: 3703404)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupPausesBeforeFirstMintEvenWhenAlreadyDistributor() (gas: 1815843)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupUsesTheLaunchsReplacementSwapper() (gas: 6693994)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 11.10ms (8.50ms CPU time)\n\nRan 6 tests for test/frens/FrenSwapperAverage.t.sol:FrenSwapperAverageTest\n[PASS] test_AFullBuyMovesItOneStep() (gas: 103298)\n[PASS] test_ASmallBuyMovesItInProportion() (gas: 52688)\n[PASS] test_DustMovesItNextToNothing() (gas: 7581882)\n[PASS] test_FloorRateIsTheLowerReading() (gas: 17216)\n[PASS] test_MovesDownToo() (gas: 52548)\n[PASS] test_OnceABlock() (gas: 91113)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 35.66ms (2.40ms CPU time)\n\nRan 8 tests for test/FrensLaunchReview.t.sol:FrensAuditReviewTest\n[PASS] test_Audit_GovernorCanCloseMintAgain() (gas: 463703)\n[PASS] test_Audit_OwnerOrGovernorCanBlockAllPeerTransfers() (gas: 1372488)\n[PASS] test_Fix_LapsedJobApprovalIsReleasedByTheReveal() (gas: 1034129)\n[PASS] test_Fix_LastFrenOutStaysOutSoFeesHaveAnOwner() (gas: 1171597)\n[PASS] test_Fix_LaunchAdoptsASwapperSeededAtThePoolsPrice() (gas: 1868092)\n[PASS] test_Fix_LaunchRefusesTheWrongArt() (gas: 18811)\n[PASS] test_Fix_LaunchReplacesASwapperSeededOffASkewedPrice() (gas: 2596387)\n[PASS] test_Fix_LaunchsOwnSwapperStartsAtThePoolsPrice() (gas: 236724)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 42.76ms (4.89ms CPU time)\n\nRan 18 tests for test/frens/FrenWorkerGate.t.sol:FrenWorkerGateTest\n[PASS] test_ARefusedMintTakesNothing() (gas: 104998)\n[PASS] test_AnNftCountsOnce_whoeverHoldsItLater() (gas: 151129)\n[PASS] test_CreditsGoWhereTheHolderSays() (gas: 639709)\n[PASS] test_NoGateNoWindow() (gas: 442112)\n[PASS] test_OnlyFrensSpends() (gas: 32613)\n[PASS] test_OnlyTheHolderClaims() (gas: 67063)\n[PASS] test_OpenPublic() (gas: 528980)\n[PASS] test_OwnerMintsBeforeTheOpening() (gas: 2296674)\n[PASS] test_SeatOperatorClaimsTheStrategysNfts() (gas: 138514)\n[PASS] test_TheCreditIsTheMinters() (gas: 576408)\n[PASS] test_WindowClosesAfter420() (gas: 32491871)\n[PASS] test_WindowNeedsACreditPerFren() (gas: 714638)\n[PASS] test_WindowNeverOvershoots() (gas: 36197537)\n[PASS] test_WlAndWorkersShareThe420() (gas: 619421)\n[PASS] test_WlOnlyAsListed_once() (gas: 254028)\n[PASS] test_WlRaisedAmount_claimsTheDifference() (gas: 226858)\n[PASS] test_WlRootOnlyTheOwner_andNoRootNoWl() (gas: 62252)\n[PASS] test_WlWalletsMintInTheWindow_asMuchAsListed() (gas: 751030)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 62.95ms (18.45ms CPU time)\n\nRan 20 tests for test/FrensMintPayments.t.sol:FrensMintPaymentsTest\n[PASS] testFuzz_BuyImdReturnsAllOutputAndUnusedEth(uint256,uint256) (runs: 256, μ: 360097, ~: 360363)\nLogs:\n  Bound result 692068886285989451701400\n  Bound result 1\n\n[PASS] testFuzz_CallbacksRejectOutsidersBeforeDecoding(bytes) (runs: 128, μ: 130692, ~: 121518)\n[PASS] testFuzz_ExactPaymentRefundAndHookSurplus(uint256,uint256,uint256,uint256) (runs: 256, μ: 1667759, ~: 1651843)\nLogs:\n  Bound result 53\n  Bound result 403042966582667559\n  Bound result 121619632060432464\n  Bound result 10450045646547226\n\n[PASS] test_BuyImdRefundCannotReenter() (gas: 561324)\n[PASS] test_BuyImdRefundRefusalRollsBackTheSwap() (gas: 720561)\n[PASS] test_BuyImdShortFillAndEthShortageAreAtomic() (gas: 805668)\n[PASS] test_ClosedMintRevertsTheAlreadyCompletedSwap() (gas: 574841)\n[PASS] test_EthCannotBorrowThePaymentTokensTier() (gas: 786728)\n[PASS] test_LastFrenMintsAndSoldOutCannotCharge() (gas: 63892535)\n[PASS] test_MaxPayOneWeiShortFailsBeforePoolEntry() (gas: 333700)\n[PASS] test_NoCreditRevertsTheAlreadyCompletedSwap() (gas: 551447)\n[PASS] test_OneWeiEthAndExactBudget() (gas: 679525)\n[PASS] test_PartialExactOutputCannotMintOrCharge() (gas: 617841)\n[PASS] test_QuoteAndMintBubbleUnexpectedPoolFailure() (gas: 727821)\n[PASS] test_QuoteReturnsNumbersWithoutMovingAnyState() (gas: 479470)\n[PASS] test_RefundCannotReenterFromMintIntoBuyImd() (gas: 949933)\n[PASS] test_RefundRefusalRollsBackMintAndCreditSpend() (gas: 1173515)\n[PASS] test_WorkerWindowOvershootRollsBackThenExactRemainderClosesIt() (gas: 13295348)\n[PASS] test_ZeroAndSeventyFrensCannotCharge() (gas: 982106)\n[PASS] test_ZeroEthAndOneWeiShortAreAtomic() (gas: 1003387)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 64.32ms (134.75ms CPU time)\n\nRan 3 tests for test/FrensLaunchReview.t.sol:FrensFactoryReviewTest\n[PASS] test_Audit_ConstructorLeavesTheDocumentedTeamSetup() (gas: 25152531)\n[PASS] test_FreshChainCallsOnlyContractsCreatedByTheLaunch() (gas: 28519889)\n[PASS] test_ProtectedFactoryDeploysBothLaunchesInOrder() (gas: 25351455)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 79.75ms (14.83ms CPU time)\n\nRan 84 tests for test/frens/IMD6900Frens.t.sol:IMD6900FrensTest\n[PASS] test_AMintNeverFailsOnTheSwap() (gas: 946501)\n[PASS] test_BatchCannotRepeatAFren() (gas: 641777)\n[PASS] test_BatchCountIsOneTo69() (gas: 2185636)\n[PASS] test_BatchCountsTheCapsAsItGoes() (gas: 11785283)\n[PASS] test_BatchHoldsPepesForLowTiers() (gas: 12180874)\n[PASS] test_BatchPaysOneJob() (gas: 700293)\n[PASS] test_BatchRevealIsExactlyTheVoucher() (gas: 1012095)\n[PASS] test_BatchRevealsThemAll() (gas: 2254191)\n[PASS] test_BatchSoldOut() (gas: 90765188)\n[PASS] test_BuyTreasuryRespectsMaxPay() (gas: 1807199)\n[PASS] test_BuyingFromTheTreasuryRaisesTheFloor() (gas: 3060520)\n[PASS] test_CurveWhileAboveTheFloor() (gas: 1591127)\n[PASS] test_DearerImd6900RaisesTheFloorPrice() (gas: 2846975)\n[PASS] test_DustEthBuyCannotTakeTheMintsTurn() (gas: 539958)\n[PASS] test_EveryFrenIsOneOfAKind() (gas: 947054)\n[PASS] test_FailedJob_payAnother() (gas: 1168121)\n[PASS] test_FeeEthBuysTheFloor() (gas: 1702668)\n[PASS] test_FloorBuysAreAnyones_onePerBlock() (gas: 1886993)\n[PASS] test_FloorBuysAreCapped() (gas: 55657779)\n[PASS] test_FloorIsReserveOverFrensOut() (gas: 2749300)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 2110486)\n[PASS] test_FrenSentStraightToTheTreasuryCounts() (gas: 2605187)\n[PASS] test_IsACreatorToken() (gas: 41743)\n[PASS] test_JobPaymentIsPinnedToImdsPayee() (gas: 974626)\n[PASS] test_KeeperApprovesTheJobPaidFor() (gas: 653212)\n[PASS] test_KeeperCannotApproveLongLivedPayments() (gas: 434010)\n[PASS] test_LapsedPaymentIsUsedAgain() (gas: 715229)\n[PASS] test_LastFrenOutStaysOut() (gas: 1582490)\n[PASS] test_LaterPartsAgreeOnWhatIsRevealed() (gas: 1347460)\n[PASS] test_LowTierRequestsNeedAPepeLeft() (gas: 11712756)\n[PASS] test_MaxMintGrowsWithTheBag() (gas: 4682103)\n[PASS] test_MaxPayStopsAPriceThatMoved() (gas: 10066140)\n[PASS] test_MaxRequest() (gas: 9919282)\n[PASS] test_MinTierOnlyGoesDown() (gas: 138825)\n[PASS] test_MintMintsAtOnce_unrevealed() (gas: 770570)\n[PASS] test_MintNeedsSealedTraits() (gas: 8627562)\n[PASS] test_MintNeverBelowTheFloor() (gas: 3403449)\n[PASS] test_MintNeverSkipsTheBuyForWantOfGas() (gas: 708859)\n[PASS] test_NoMintIntoTheTreasury() (gas: 1590016)\n[PASS] test_NoRequestForNobody() (gas: 61934)\n[PASS] test_NoTierInsideAFlashLoan() (gas: 687603)\n[PASS] test_NobodyElseApprovesJobs() (gas: 435602)\n[PASS] test_OneRevealPerFren() (gas: 717815)\n[PASS] test_OnlyHoldersRecycle() (gas: 1587638)\n[PASS] test_OnlyRealFrens() (gas: 743528)\n[PASS] test_OnlyTheOwnerSetsTheValidator() (gas: 1060220)\n[PASS] test_OwnerAdjustsTheArtUntilFrozen() (gas: 1552272)\n[PASS] test_PairRules() (gas: 875609)\n[PASS] test_PartsOnlyGoForward() (gas: 1035574)\n[PASS] test_PriceRisesWithMints() (gas: 38121727)\n[PASS] test_PriceTableIsTheCurve() (gas: 37031661)\n[PASS] test_QuoteCountsUnsweptImd() (gas: 3192315)\n[PASS] test_RareTraitsNeedTheTier() (gas: 1366007)\n[PASS] test_RecyclePaysTheFloorAndKeepsIt() (gas: 2892194)\n[PASS] test_RecyclePaysTheWaitingImdToo() (gas: 2719969)\n[PASS] test_ReleaseLapsedJobAfterTheReveal() (gas: 2133093)\n[PASS] test_RequestMintFor() (gas: 1084049)\n[PASS] test_ReserveOnlyLeavesThroughRecycle() (gas: 2826030)\n[PASS] test_RevealFollowsTheFren() (gas: 749295)\n[PASS] test_RevealInParts() (gas: 9744736)\nLogs:\n  gas, minting 69: 2117876\n  gas, revealing 30: 3092889\n\n[PASS] test_RevealNeedsTheRelayersVoucher() (gas: 803728)\n[PASS] test_RevealReleasesALapsedPayment() (gas: 916235)\n[PASS] test_RevealShowsTheFren() (gas: 1189238)\n[PASS] test_RevealsWhenever() (gas: 661569)\n[PASS] test_RoyaltiesPayTheFloor() (gas: 1718930)\n[PASS] test_RoyaltyBpsIsCappedAndOwnerOnly() (gas: 109478)\n[PASS] test_RulesAreFrozenOnceSealed() (gas: 69602)\n[PASS] test_SealNeedsEveryTraitAndTheSupply() (gas: 10522996)\n[PASS] test_SetMaxMint() (gas: 178348)\n[PASS] test_ShortSwapReverts() (gas: 1043815)\n[PASS] test_SwapperMayPullOnlyTheBuy() (gas: 954501)\n[PASS] test_TheOwnerKeepsTheCollection_theGovernorTheMechanics() (gas: 488972)\n[PASS] test_TierCountsWhatIsLeftAfterPaying() (gas: 919870)\n[PASS] test_TierIsTheBestOfTheBag() (gas: 633637)\n[PASS] test_TierIsTheMints() (gas: 1350662)\n[PASS] test_TierThresholdsOwnerOnlyAndOrdered() (gas: 155016)\n[PASS] test_TradesGoPastTheValidator() (gas: 1343397)\n[PASS] test_TraitCapsHold() (gas: 25774957)\n[PASS] test_TreasuryRoundTripCostsTheFloor() (gas: 3239545)\n[PASS] test_UnneededRetryFeedsTheFloor() (gas: 1023226)\n[PASS] test_UnrevealedFrenSellsAtTheFloor() (gas: 1015894)\n[PASS] test_UnrevealedFrensShareTheFloor() (gas: 1451826)\n[PASS] test_UnusedJobFeedsTheFloor() (gas: 697059)\n[PASS] test_VouchersExpire() (gas: 472525)\nSuite result: ok. 84 passed; 0 failed; 0 skipped; finished in 79.77ms (179.06ms CPU time)\n\nRan 12 tests for test/FrensArtFraming.t.sol:FrensArtFramingTest\n[PASS] testFuzz_EntryPastTheIndexIsMissing(uint256) (runs: 256, μ: 13978, ~: 13943)\nLogs:\n  Bound result 36242589672662845076499235437944023186313497121178005792\n\n[PASS] testFuzz_UnknownBackgroundIsMissing(uint256) (runs: 256, μ: 35611, ~: 35592)\nLogs:\n  Bound result 36242589672662845076499235437944023186313497121178005792\n\n[PASS] test_ATamperedChunkDrawsNothing() (gas: 90530)\n[PASS] test_AttributesDoNotValidate() (gas: 369298)\n[PASS] test_ChunksAreFramedPushData() (gas: 2731712)\n[PASS] test_EveryValueHasAName() (gas: 15679313)\n[PASS] test_IndexHashesAreTheChunks() (gas: 788314)\n[PASS] test_IndexPointsInsideTheChunks() (gas: 525933)\n[PASS] test_LaunchLayersAreWellFormed() (gas: 46186857)\n[PASS] test_NoSuchFrenIsMissing() (gas: 4375022)\n[PASS] test_PalettesFitTogether() (gas: 13989817)\n[PASS] test_WithoutTheSwarmNothingDraws() (gas: 5947823)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 82.98ms (249.56ms CPU time)\n\nRan 19 tests for test/FrensLaunchFailures.t.sol:FrensLaunchFailuresTest\n[PASS] testFuzz_CheckKnowsAFren(uint24,uint8) (runs: 512, μ: 1935930, ~: 1907693)\nLogs:\n  Bound result 1\n\n[PASS] testFuzz_OtherSaltsLandElsewhere(bytes32) (runs: 256, μ: 34945, ~: 34945)\n[PASS] testFuzz_QuoteIsTheCurveBeforeAnyoneHolds(uint256) (runs: 512, μ: 318255, ~: 318489)\nLogs:\n  Bound result 24\n\n[PASS] test_AnotherOwnerMovesTheFrens() (gas: 34214)\n[PASS] test_CollectionRefusesAShortPriceTable() (gas: 3059925)\n[PASS] test_CollectionWaitsForTheTeamWallet() (gas: 5941920)\n[PASS] test_EveryPriceIsTheTables() (gas: 27629816)\n[PASS] test_FreshChainLaunchesAreIndependent() (gas: 1351085)\n[PASS] test_LaunchContractsTakeNoEth() (gas: 687657)\n[PASS] test_ModulesFailOverCorruptedFrens() (gas: 97101)\n[PASS] test_ModulesNeedTheCollection() (gas: 106270)\n[PASS] test_NoLaunchActorHoldsARole() (gas: 4583570)\n[PASS] test_NoPricePastTheTable() (gas: 41307)\n[PASS] test_OtherSaltSchemeFailsTheLaunch() (gas: 140656)\n[PASS] test_RefusingDeployerFailsTheLaunch() (gas: 103369)\n[PASS] test_ScriptRefusesAnUnplacedChain() (gas: 14619610)\n[PASS] test_SecondLaunchReusesWhatIsPlaced() (gas: 1313470)\n[PASS] test_SilentDeployerFailsTheLaunch() (gas: 93234)\n[PASS] test_WrongArtFailsTheArtLaunch() (gas: 588910)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 94.68ms (285.18ms CPU time)\n\nRan 13 tests for test/FrensPlacement.t.sol:FrensPlacementTest\n[PASS] test_Attributes() (gas: 19594305)\n[PASS] test_CodeIsWhatTheSourcesBuild() (gas: 88334)\n[PASS] test_DeploysOnAFreshChain() (gas: 51532495)\n[PASS] test_EachLaunchFitsOneTransaction() (gas: 105289625)\nLogs:\n  the collection launch: gas, all in: 12308310\n  the art launch: gas, all in: 13605058\n\n[PASS] test_LandsWhereThePlanSays() (gas: 51643509)\n[PASS] test_NewArtReadsBackExactly() (gas: 14173584)\n[PASS] test_PassesTheAdmissionScan() (gas: 87697392)\n[PASS] test_PlanFollowsFromTheCode() (gas: 79539)\n[PASS] test_PlanMatchesTheScript() (gas: 14147485)\n[PASS] test_PriceTableIsTheCurve() (gas: 52824571)\n[PASS] test_RefusesOtherArt() (gas: 12971045)\n[PASS] test_SameAddressesWhoeverRunsIt() (gas: 102213411)\n[PASS] test_TakesWhatSomeonePlacedFirst() (gas: 52211127)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 101.43ms (168.85ms CPU time)\n\nRan 1 test for test/FrensSplitGas.t.sol:FrensSplitGasTest\n[PASS] test_CollectionFitsButLegacyFourContractBundleExceedsCap() (gas: 100924258)\nLogs:\n  legacy four-contract launch: 23723070\n  two-contract collection launch: 13340309\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 108.59ms (108.43ms CPU time)\n\nRan 1 test for test/FrensPlacedInvariants.t.sol:FrensPlacedInvariantsTest\n[PASS]\nFrensPlacedInvariantsTest invariants:\n[PASS] invariant_CapsHold\n[PASS] invariant_FloorIsAShareOfTheWorld\n[PASS] invariant_FrensAreConserved\n[PASS] invariant_ImdIsTheBooks\n[PASS] invariant_ReserveIsExactlyWhatIsHere\n FrensPlacedInvariantsTest invariants (runs: 32, calls: 1920, reverts: 4)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| PlacedFrensHandler | approveJob      | 113   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyFloor        | 121   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyTreasury     | 120   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donate6900      | 129   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donateImd       | 117   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | feeEth          | 105   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mint            | 254   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mintThenRecycle | 115   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | recycle         | 111   | 4       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | retryJob        | 132   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | reveal          | 133   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setParams       | 112   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setRate         | 126   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | transfer        | 124   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | warp            | 108   | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 39\n  Bound result 3248\n  Bound result 7783\n  Bound result 1\n  Bound result 36\n  Bound result 12\n  Bound result 1068\n  Bound result 57\n  Bound result 0\n  Bound result 2513787314\n  Bound result 0\n  Bound result 2\n  Bound result 22276\n  Bound result 6148\n  Bound result 2618\n  Bound result 0\n  Bound result 2\n  Bound result 417531900361828837441552\n  Bound result 30\n  Bound result 2753\n  Bound result 746\n  Bound result 8\n  Bound result 3279\n  Bound result 561\n  Bound result 15\n  Bound result 601\n  Bound result 1\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2218\n  Bound result 8\n  Bound result 3114\n  Bound result 15\n  Bound result 33\n  Bound result 265890\n  Bound result 30\n  Bound result 2400\n  Bound result 17\n  Bound result 2\n  Bound result 881109199508619882788970\n  Bound result 696312\n  Bound result 62\n  Bound result 0\n  Bound result 19778\n  Bound result 156302528682659073\n  Bound result 17850\n  Bound result 0\n  Bound result 2223\n  Bound result 10097\n  Bound result 2\n  Bound result 1795\n  Bound result 1364\n  Bound result 3096268767\n  Bound result 72\n  Bound result 10916\n  Bound result 16919\n  Bound result 1\n  Bound result 52\n  Bound result 17716\n  Bound result 75\n  Bound result 22\n  Bound result 2786\n  Bound result 23\n  Bound result 3067\n  Bound result 51\n  Bound result 2400\n  Bound result 2\n  Bound result 23331\n  Bound result 161\n  Bound result 7864320\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.71s (2.70s CPU time)\n\nRan 2 tests for test/FrensMintPaymentInvariants.t.sol:FrensMintPaymentInvariantsTest\n[PASS]\nFrensMintPaymentInvariantsTest invariants:\n[PASS] invariant_AllEthAndImdAreAccountedFor\n[PASS] invariant_CreditsAndSupplyMatchSuccessfulPayments\n FrensMintPaymentInvariantsTest invariants (runs: 256, calls: 20480, reverts: 0)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| MintPaymentHandler | buyImd          | 2838  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | claim           | 2899  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | governance      | 3013  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | mint            | 2885  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | quote           | 2975  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | rejectedPayment | 2917  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| MintPaymentHandler | unauthorized    | 2953  | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 15\n  Bound result 22\n  Bound result 1000000\n  Bound result 512\n  Bound result 54\n  Bound result 399999999999999994\n  Bound result 32\n  Bound result 19827\n  Bound result 13942\n  Bound result 46\n  Bound result 5280\n  Bound result 1000\n  Bound result 43\n  Bound result 40\n  Bound result 42\n  Bound result 16880\n  Bound result 250000000000000000\n  Bound result 6144\n  Bound result 46\n  Bound result 912\n  Bound result 309\n  Bound result 21000\n  Bound result 333108623535389669\n  Bound result 6938\n  Bound result 44\n  Bound result 2021\n  Bound result 228313851\n  Bound result 17\n  Bound result 821599673257294719\n  Bound result 18669\n  Bound result 7056\n  Bound result 13\n  Bound result 1901074597\n  Bound result 55\n  Bound result 559164758310567231\n  Bound result 16896\n  Bound result 138\n  Bound result 4294967040\n  Bound result 421\n  Bound result 62\n  Bound result 7386663529749340101305\n  Bound result 500817115951618078\n  Bound result 18931401080455795\n  Bound result 564\n  Bound result 56\n  Bound result 18817\n  Bound result 50\n  Bound result 476\n  Bound result 499\n  Bound result 68\n  Bound result 11307\n  Bound result 3600\n  Bound result 548\n  Bound result 222\n  Bound result 411\n  Bound result 358\n  Bound result 54\n  Bound result 12\n  Bound result 658\n  Bound result 25\n  Bound result 3000\n  Bound result 999999002\n  Bound result 43\n  Bound result 10\n  Bound result 3509\n  Bound result 10472\n  Bound result 16\n  Bound result 293\n  Bound result 29\n  Bound result 882423685733893130\n  Bound result 167914848\n  Bound result 69\n  Bound result 332954047909487102\n  Bound result 141\n  Bound result 2\n  Bound result 7586\n  Bound result 13009\n  Bound result 20\n  Bound result 2331\n  Bound result 7292\n  Bound result 12226\n  Bound result 3\n  Bound result 999999000000000001\n  Bound result 990000002\n  Bound result 851\n  Bound result 53\n  Bound result 57\n  Bound result 13\n  Bound result 147\n  Bound result 79516157\n  Bound result 597\n  Bound result 632\n  Bound result 8\n  Bound result 10000000000000000\n  Bound result 65\n  Bound result 32\n  Bound result 26880\n  Bound result 82\n\n[PASS] test_HandlerExercisesSuccessRejectionAndPermanentOpening() (gas: 5645112)\nLogs:\n  Bound result 100\n  Bound result 69\n  Bound result 1\n  Bound result 7\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 1\n  Bound result 1\n  Bound result 0\n  Bound result 1\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 0\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.18s (9.17s CPU time)\n\nRan 19 test suites in 9.18s (12.66s CPU time): 211 tests passed, 0 failed, 5 skipped (216 total tests)\n","passed":true},{"durationMs":99,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrenMinter.buyImd(uint256)\",\"FrenMinter.mintWithEth(uint8,uint256)\",\"FrenMinter.quoteEth(uint8)\",\"FrenMinter.quoteFloor(uint256)\",\"FrenMinter.quoteFloorEth(uint256)\",\"FrenMinter.unlockCallback(bytes)\",\"FrenSwapper.ethToImd6900(uint256,address)\",\"FrenSwapper.imdToImd6900(uint256,uint256,address)\",\"FrenSwapper.unlockCallback(bytes)\",\"FrenWorkerGate.cancelOwnershipHandover()\",\"FrenWorkerGate.claim(uint256[],address)\",\"FrenWorkerGate.claimWl(uint256,bytes32[],address)\",\"FrenWorkerGate.completeOwnershipHandover(address)\",\"FrenWorkerGate.openPublic()\",\"FrenWorkerGate.renounceOwnership()\",\"FrenWorkerGate.requestOwnershipHandover()\",\"FrenWorkerGate.setWlRoot(bytes32)\",\"FrenWorkerGate.spend(address,uint256)\",\"FrenWorkerGate.transferOwnership(address)\",\"IMD6900Frens.addPairRule((uint8,uint8,uint8,uint8,uint8))\",\"IMD6900Frens.approve(address,uint256)\",\"IMD6900Frens.approveJob(uint256,uint256,uint256,(string,bytes32,string,bytes32,bytes32,string,uint256))\",\"IMD6900Frens.buyFloor(uint256)\",\"IMD6900Frens.buyFloorWithEth(uint256,uint256)\",\"IMD6900Frens.buyTreasury(uint256,uint256,uint256)\",\"IMD6900Frens.cancelOwnershipHandover()\",\"IMD6900Frens.completeOwnershipHandover(address)\",\"IMD6900Frens.freezeArt()\",\"IMD6900Frens.lowerMinTier(uint8,uint8,uint8)\",\"IMD6900Frens.receive()\",\"IMD6900Frens.recycle(uint256)\",\"IMD6900Frens.releaseLapsedJob(uint256)\",\"IMD6900Frens.renounceOwnership()\",\"IMD6900Frens.requestMint(uint8,uint256)\",\"IMD6900Frens.requestMintFor(address,uint8,uint256)\",\"IMD6900Frens.requestOwnershipHandover()\",\"IMD6900Frens.retryJob(uint256)\",\"IMD6900Frens.reveal(uint256,uint24[],string,bytes32,uint256,bytes,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256,bytes)\",\"IMD6900Frens.sealTraits()\",\"IMD6900Frens.setApprovalForAll(address,bool)\",\"IMD6900Frens.setGovernor(address)\",\"IMD6900Frens.setMaxMint(uint8[4])\",\"IMD6900Frens.setMintOpen(bool)\",\"IMD6900Frens.setModules(address,address)\",\"IMD6900Frens.setParams(uint256,uint256,uint256)\",\"IMD6900Frens.setRenderer(address)\",\"IMD6900Frens.setRoles(address,address,address)\",\"IMD6900Frens.setRoyalty(uint96)\",\"IMD6900Frens.setTiers(uint256[3],uint256[3],uint256[3])\",\"IMD6900Frens.setTraitRules(uint8,uint16[],uint8[])\",\"IMD6900Frens.setTransferValidator(address)\",\"IMD6900Frens.transferFrom(address,address,uint256)\",\"IMD6900Frens.transferOwnership(address)\",\"IMD6900Frens.unwrapWeth()\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":145,\"README.md\":181,\"foundry.toml\":22,\"launch.json\":16,\"script/art/chunks.py\":165,\"script/art/data/bgpal/cleanlab_blue.bin\":1,\"script/art/data/bgpal/cleanlab_green.bin\":1,\"script/art/data/bgpal/cleanlab_red.bin\":1,\"script/art/data/bgpal/messylab_blue.bin\":1,\"script/art/data/bgpal/messylab_green.bin\":1,\"script/art/data/bgpal/messylab_red.bin\":1,\"script/art/data/bgpal/tubeblue.bin\":1,\"script/art/data/bgpal/tubegreen.bin\":2,\"script/art/data/bgpal/tubered.bin\":1,\"script/art/data/bgpal/tubeyellow.bin\":1,\"script/art/data/bgpal/wireframe_green.bin\":2,\"script/art/data/bgpal/wireframe_red.bin\":1,\"script/art/data/expected.json\":53,\"script/art/data/layers/bg0.bin\":9,\"script/art/data/layers/bg1.bin\":9,\"script/art/data/layers/bg10.bin\":32,\"script/art/data/layers/bg11.bin\":32,\"script/art/data/layers/bg2.bin\":9,\"script/art/data/layers/bg3.bin\":15,\"script/art/data/layers/bg4.bin\":15,\"script/art/data/layers/bg5.bin\":15,\"script/art/data/layers/bg6.bin\":26,\"script/art/data/layers/bg7.bin\":26,\"script/art/data/layers/bg8.bin\":26,\"script/art/data/layers/bg9.bin\":26,\"script/art/data/layers/coat.bin\":20,\"script/art/data/layers/item5.bin\":2,\"script/art/data/manifest.json\":500,\"script/art/data/shared.bin\":4,\"script/art/data/tables.bin\":2,\"script/art/swarm-v2.json\":374,\"script/frens/DeployFrens.s.sol\":243,\"script/frens/FrensTimelockBatch.s.sol\":105,\"script/frens/price/prices-swarm.bin\":6,\"script/frens/price/prices.bin\":6,\"script/frens/price/prices.json\":26,\"script/frens/price/prices.py\":55,\"script/frens/price/prices_sol.py\":33,\"script/placement/addresses.json\":7,\"script/placement/gen.py\":135,\"src/FrensCode.sol\":15,\"src/FrensPlacement.sol\":129,\"src/FrensPlan.sol\":37,\"src/art/WorkerArt.sol\":671,\"src/art/WorkerArtIndex.sol\":56,\"src/frens/FrenMinter.sol\":194,\"src/frens/FrenPrices.sol\":122,\"src/frens/FrenSwapper.sol\":249,\"src/frens/FrenWorkerGate.sol\":137,\"src/frens/IMD6900Frens.sol\":1072,\"src/frens/WorkerFrensRenderer.sol\":377,\"test/FrensArtFraming.t.sol\":290,\"test/FrensLaunchAdaptation.t.sol\":317,\"test/FrensLaunchFailures.t.sol\":526,\"test/FrensLaunchReview.t.sol\":449,\"test/FrensMintPaymentInvariants.t.sol\":335,\"test/FrensMintPayments.md\":45,\"test/FrensMintPayments.t.sol\":299,\"test/FrensPlacedInvariants.t.sol\":619,\"test/FrensPlacement.t.sol\":818,\"test/FrensSplitGas.t.sol\":68,\"test/frens/DataWriter.sol\":21,\"test/frens/FrenMinter.fork.t.sol\":189,\"test/frens/FrenSwapper.fork.t.sol\":134,\"test/frens/FrenSwapperAverage.t.sol\":77,\"test/frens/FrenWorkerGate.t.sol\":350,\"test/frens/FrensRules.sol\":75,\"test/frens/IMD6900Frens.t.sol\":1710,\"test/frens/IMD6900FrensPermit2.fork.t.sol\":130,\"test/frens/IMD6900FrensValidator.fork.t.sol\":146,\"test/frens/IMD6900FrensValidator.t.sol\":231,\"test/helpers/FrensEthFixture.sol\":196,\"test/test_collection_manifest.py\":28},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"7bb0b60c9bace7a5a316536a27d797cb9c7a383a970e34701b94213834db5229","verifiedTreeHash":"9afae8eeb94653e7d43fef931ac527cef2a2a850","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":150282,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.30\nSolc 0.8.30 finished in 150.07s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n   --> test/FrensPlacement.t.sol:786:9:\n    |\n786 |         uint256 s = _find(j, key, 0) + key.length;\n    |         ^^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/FrensPlacement.t.sol:477:5:\n    |\n477 |     DeployFrens s;\n    |     ^^^^^^^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to view\n   --> test/FrensLaunchAdaptation.t.sol:184:5:\n    |\n184 |     function test_PlacedReportsTheLaunchModules() public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/frens/IMD6900Frens.sol:953:66\n    │\n953 │         return (ITransferValidator721.validateTransfer.selector, true);\n    │                                                                  ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:77\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                                             ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:55\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenMinter.sol:51:39\n   │\n51 │     constructor(address poolManager_, address frens_, address imdPoolHook_, address pairHook_) {\n   │                                       ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:68:9\n   │\n68 │         address imd6900_,\n   │         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:69:9\n   │\n69 │         address frens_,\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:71:9\n   │\n71 │         address imdPoolHook_\n   │         ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:67:9\n   │\n67 │         address imd_,\n   │         ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenSwapper.sol:70:9\n   │\n70 │         address pairHook_,\n   │         ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-events-access-control]: `keeper` is changed without an event but is used for access control\n    ╭▸ src/frens/IMD6900Frens.sol:998:36\n    │\n998 │         if (keeper_ != address(0)) keeper = keeper_;\n    │                                    ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `governor` is changed without an event but is used for access control\n     ╭▸ src/frens/IMD6900Frens.sol:1064:9\n     │\n1064 │         governor = g;\n     │         ━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:150:16\n    │\n150 │         return uint160(zeroForOne ? uint256(p) * 1e18 / f : uint256(p) * f / 1e18);\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:54:13\n   │\n54 │ ┏             abi.encodePacked(\n55 │ ┃                 FrensCode.FRENS,\n56 │ ┃                 abi.encode(\n57 │ ┃                     FrensPlan.OWNER,\n   ‡ ┃\n68 │ ┃             )\n   │ ┗━━━━━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/FrensPlacement.sol:93:29\n   │\n93 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n94 │ ┃             FrensCode.SWAPPER,\n95 │ ┃             abi.encode(\n96 │ ┃                 FrensPlan.POOL_MANAGER, FrensPlan.IMD, FrensPlan.IMD6900, f, FrensPlan.PAIR_HOOK, FrensPlan.POOL4_HOOK\n97 │ ┃             )\n98 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:109:13\n    │\n109 │ ┏             abi.encodePacked(\n110 │ ┃                 FrensCode.MINTER, abi.encode(FrensPlan.POOL_MANAGER, f, FrensPlan.POOL4_HOOK, FrensPlan.PAIR_HOOK)\n111 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/FrensPlacement.sol:115:13\n    │\n115 │             abi.encodePacked(FrensCode.GATE, abi.encode(FrensPlan.OWNER, f, FrensPlan.IDENTITY, FrensPlan.IMD6900))\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:118:35\n    │\n118 │             if (r.length == 68 && bytes4(r) == Quoted.selector) {\n    │                                   ━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:59:29\n   │\n59 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n60 │ ┃             '{\"name\":\"Worker Fren #',\n61 │ ┃             tokenId.toString(),\n62 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n68 │ ┃             \"}\"\n69 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/frens/WorkerFrensRenderer.sol:75:29\n   │\n75 │           bytes memory json = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n76 │ ┃             '{\"name\":\"Worker Fren #',\n77 │ ┃             tokenId.toString(),\n78 │ ┃             '\",\"description\":\"',\n   ‡ ┃\n82 │ ┃             '\",\"attributes\":[{\"trait_type\":\"Status\",\"value\":\"Unrevealed\"}]}'\n83 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:125:54\n    │\n125 │                     bytes memory out = new bytes(j - start);\n    │                                                      ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/WorkerFrensRenderer.sol:124:21\n    │\n124 │                 if (k == i) {\n    │                     ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/frens/WorkerFrensRenderer.sol:158:17\n    │\n158 │ ┏                 abi.encodePacked(\n159 │ ┃                     '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 84 84\" width=\"840\" height=\"840\">',\n160 │ ┃                     '<image width=\"84\" height=\"',\n161 │ ┃                     (N * PENDING_FRAMES).toString(),\n    ‡ ┃\n165 │ ┃                     'dur=\"1.3s\" calcMode=\"discrete\" repeatCount=\"indefinite\"/></image></svg>'\n166 │ ┃                 )\n    │ ┗━━━━━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:208:35\n    │\n208 │                 bytes1 v = bytes1(uint8(l * 3 / 5));\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:42\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:221:62\n    │\n221 │         _draw(cv, _entry(Art.BG0 + bg), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                              ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:138:79\n    │\n138 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                 ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:253:74\n    │\n253 │         if (ch > 2 || face >= FACES || coat > 2 || shirt > 5 || hat > 2) revert Missing();\n    │                                                                          ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:350:31\n    │\n350 │         if (i >= Art.ENTRIES) revert Missing();\n    │                               ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:356:72\n    │\n356 │         if (p.codehash != bytes32(_hex(Art.CHUNK_HASHES, c * 64, 64))) revert BadArt();\n    │                                                                        ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/WorkerFrensRenderer.sol:288:25\n    │\n288 │                         revert(28, 4)\n    │                         ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:25\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:140:33\n    │\n140 │                 imdIn = uint256(uint128(e.amount1()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:56\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                        ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:232:76\n    │\n232 │         _draw(back, _entry(Art.BG0 + Art.PENDING_BG), -int256(seed % 37), -int256((seed >> 8) % 37), none, false);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:148:39\n    │\n148 │                     amountSpecified: -int256(imdIn),\n    │                                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:239:23\n    │\n239 │             _fren(cv, uint24(combo));\n    │                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:35\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:153:43\n    │\n153 │             revert Quoted(amount, uint256(uint128(imdIs0 ? p.amount1() : p.amount0())));\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:158:70\n    │\n158 │ …     imdKey(), SwapParams({zeroForOne: true, amountSpecified: int256(imdOut), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}), \"\"\n    │                                                                ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:25\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:160:33\n    │\n160 │         uint256 ethIn = uint256(uint128(-d.amount0()));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:23\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:261:22\n    │\n261 │         int256 dx = -int256(CX);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:262:22\n    │\n262 │         int256 dy = -int256(CY);\n    │                      ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenMinter.sol:161:31\n    │\n161 │         uint256 got = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenMinter.sol:165:9\n    │\n165 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/FrenMinter.sol:183:29\n    │\n183 │             if (!withEth && cost > a) continue;\n    │                             ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/WorkerFrensRenderer.sol:321:56\n    │\n321 │         for (uint256 i; i < n; ++i) b[at + i] = bytes1(uint8(v >> (8 * i)));\n    │                                                        ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:178:17\n    │\n178 │             try frens.quote(n) returns (uint256 q) {\n    │                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:22\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:51\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:190:84\n    │\n190 │             if (a >= frens.imdTier(t - 1) || b >= frens.imd6900Tier(t - 1) || c >= frens.identityTier(t - 1)) return t;\n    │                                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/FrenMinter.sol:184:22\n    │\n184 │             if (n <= frens.maxMint(_tier(withEth ? a : a - cost, b, c))) return n;\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/frens/WorkerFrensRenderer.sol:370:49\n    │\n370 │             extcodecopy(p, add(raw, 32), add(1, mul(first, 33)), mul(frames, 33))\n    │                                                 ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:184:35\n    │\n184 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:23\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:192:31\n    │\n192 │         uint256 pay = uint256(uint128(-paid));\n    │                               ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:194:13\n    │\n194 │             poolManager.settle{value: pay}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:198:13\n    │\n198 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:23\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:200:31\n    │\n200 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:221:35\n    │\n221 │                 amountSpecified: -int256(ethIn),\n    │                                   ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:27\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:226:35\n    │\n226 │         uint256 ethPaid = uint256(uint128(-d1.amount0()));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:26\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:227:34\n    │\n227 │         uint256 imdGot = uint256(uint128(d1.amount1()));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:234:35\n    │\n234 │                 amountSpecified: -int256(imdGot),\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:27\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:240:35\n    │\n240 │         uint256 imdPaid = uint256(uint128(-paid));\n    │                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:23\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/FrenSwapper.sol:241:31\n    │\n241 │         uint256 out = uint256(uint128(got));\n    │                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/FrenSwapper.sol:243:9\n    │\n243 │         poolManager.settle{value: ethPaid}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:68\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                                                    ━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/frens/FrenWorkerGate.sol:58:33\n   │\n58 │     constructor(address owner_, address frens_, address identity_, address strategy_) {\n   │                                 ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:76:30\n   │\n76 │             address holder = identity.ownerOf(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:77:78\n   │\n77 │             if (holder != msg.sender && (holder != strategy || msg.sender != ISeatStrategyMin(strategy).seatOperator()))\n   │                                                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:79:17\n   │\n79 │                 revert NotYours(id);\n   │                 ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/frens/FrenWorkerGate.sol:81:30\n   │\n81 │             if (claimed[id]) revert AlreadyClaimed(id);\n   │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:105:9\n    │\n105 │         emit WlRootSet(root);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/FrenWorkerGate.sol:135:9\n    │\n135 │         emit PublicOpened(workerMinted);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:313:9\n    │\n313 │         address identity_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:315:9\n    │\n315 │         address x402Proxy_,\n    │         ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:311:9\n    │\n311 │         address imd_,\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:314:9\n    │\n314 │         address permit2_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:316:9\n    │\n316 │         address imdPayTo_,\n    │         ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:319:9\n    │\n319 │         address priceTable_\n    │         ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:310:9\n    │\n310 │         address owner_,\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:318:9\n    │\n318 │         address relayer_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:317:9\n    │\n317 │         address keeper_,\n    │         ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:312:9\n    │\n312 │         address imd6900_,\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:333:9\n    │\n333 │         emit ICreatorToken.TransferValidatorUpdated(address(0), DEFAULT_TRANSFER_VALIDATOR);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:370:16\n    │\n370 │         return uint8(table >> (8 * trait));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:375:16\n    │\n375 │         return uint8((uint256(combo) >> _shift(trait)) & ((1 << _bits(trait)) - 1));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:384:38\n    │\n384 │             if (minTiers[v] > TIERS) revert BadTraits();\n    │                                      ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:406:13\n    │\n406 │             chars += _rules[v].cap;\n    │             ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:369:30\n    │\n369 │         if (trait >= TRAITS) revert BadTraits();\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-no-eth]: external call can be reentered before `floorImd` is updated\n    ╭▸ src/frens/IMD6900Frens.sol:843:13\n    │\n843 │         try IFrenSwapper(swapper).imdToImd6900{gas: FLOOR_BUY_GAS}(imdIn, minOut, address(this)) {}\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:974:9\n    │\n974 │         ITransferValidator721(validator).validateTransfer(msg.sender, from, to, id);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:568:9\n    │\n568 │         emit FrensMinted(requestId, minter, first, count, tier, paid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:853:9\n    │\n853 │         emit FloorBought(spent, 0, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:968:13\n    │\n968 │             revert LastFrenOut();\n    │             ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:564:82\n    │\n564 │         requests[requestId] = Request(minter, tier, lowTier, false, count, 0, 1, uint32(first), 0, 0);\n    │                                                                                  ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/frens/IMD6900Frens.sol:630:38\n    │\n630 │             h = keccak256(abi.encode(h, combos[i]));\n    │                                      ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:622:13\n    │\n622 │         if (block.timestamp > deadline) revert BadVoucher();\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/frens/IMD6900Frens.sol:650:13\n    │\n650 │             emit Revealed(requestId, tokenId, combo, jobId, outputHash);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:639:28\n    │\n639 │             if (code != 0) revert BadCombo(code);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/frens/IMD6900Frens.sol:641:90\n    │\n641 │             if (valueOf(combo, CHARACTER) == 0 && pepe.minted + openLowTier >= pepe.cap) revert BadCombo(3);\n    │                                                                                          ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:634:22\n    │\n634 │         r.revealed = uint8(upTo);\n    │                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/frens/IMD6900Frens.sol:672:53\n    │\n672 │         if (!r.jobApproved || _spent(r.jobNonce) || block.timestamp <= r.jobDeadline) return false;\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/frens/IMD6900Frens.sol:696:9\n    │\n696 │         emit JobPaid(requestId, msg.sender);\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/frens/IMD6900Frens.sol:722:13\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:722:53\n    │\n722 │         if (deadline > block.timestamp + 1 hours || q.expiresAt > block.timestamp + 1 hours) revert BadJob();\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/frens/IMD6900Frens.sol:738:9\n    │\n738 │         emit JobApproved(requestId, nonce, deadline, permitDigest, quoteDigest);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/frens/IMD6900Frens.sol:729:25\n    │\n729 │         r.jobDeadline = uint40(deadline);\n    │                         ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:889:9\n    │\n889 │         emit FloorBought(imdIn, ethIn, got, reserve);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/frens/IMD6900Frens.sol:867:9\n    │\n867 │         IFrenSwapper(swapper).ethToImd6900{value: ethIn}(minOut, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:882:9\n    │\n882 │         emit WethUnwrapped(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:904:9\n    │\n904 │         emit Recycled(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:923:9\n    │\n923 │         emit TreasuryBought(tokenId, msg.sender, paid, imdPaid);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/frens/IMD6900Frens.sol:944:35\n    │\n944 │     function setTransferValidator(address validator) external onlyCurator {\n    │                                   ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/frens/IMD6900Frens.sol:946:9\n    │\n946 │         emit ICreatorToken.TransferValidatorUpdated(getTransferValidator(), validator);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n     ╭▸ src/frens/IMD6900Frens.sol:1005:26\n     │\n1005 │     function setRenderer(address r) external onlyCurator {\n     │                          ━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1008:9\n     │\n1008 │         emit Setting(\"renderer\", 0, r);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1014:9\n     │\n1014 │         emit Setting(\"artFrozen\", 1, renderer);\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1030:17\n     │\n1030 │                 revert BadTraits();\n     │                 ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n     ╭▸ src/frens/IMD6900Frens.sol:1042:84\n     │\n1042 │             if (m[i] == 0 || m[i] > MAX_PER_REQUEST || (i > 0 && m[i] < m[i - 1])) revert BadTraits();\n     │                                                                                    ━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n     ╭▸ src/frens/IMD6900Frens.sol:1058:9\n     │\n1058 │         emit Setting(\"royalty\", bps, address(0));\n     │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n     │\n     ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/frens/IMD6900Frens.t.sol:527:21\n    │\n527 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n528 │         bytes memory sig = _sign(frens, relayerKey, id, c, d);\n529 │         vm.warp(d + 1);\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/frens/IMD6900Frens.t.sol:537:17\n    │\n537 │         vm.warp(block.timestamp + 30 days);\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/frens/IMD6900Frens.t.sol:682:50\n    │\n682 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:683:17\n    │\n683 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:696:17\n    │\n696 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:718:50\n    │\n718 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:719:17\n    │\n719 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:734:50\n    │\n734 │         (bytes32 pd,) = frens.approveJob(id, 42, block.timestamp + 600, _quote());\n    │                                                  ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:278:21\n    │\n278 │         uint256 d = block.timestamp + 1 hours;\n    │                     ━━━━━━━━━━━━━━━\n    ‡\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:739:17\n    │\n739 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:750:17\n    │\n750 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:267:34\n    │\n267 │         frens.approveJob(id, id, block.timestamp + 600, _quote());\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:261:13\n    │\n261 │             block.timestamp + 600\n    │             ━━━━━━━━━━━━━━━\n    ‡\n757 │         vm.warp(block.timestamp + 601);\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/frens/IMD6900Frens.t.sol:757:17\n    │\n757 │         vm.warp(block.timestamp + 601);\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/FrensLaunchAdaptation.t.sol:284:26\n    │\n284 │         uint256 expiry = block.timestamp + 600;\n    │                          ━━━━━━━━━━━━━━━\n    ‡\n291 │         vm.warp(expiry + 1);\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.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:322:17\n    │\n322 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/FrensPlacedInvariants.t.sol:357:17\n    │\n357 │         vm.roll(block.number + bound(blocks, 0, 2));\n    │         ────────━━━━━━━━━━━━─────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2760,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/frens/IMD6900FrensValidator.fork.t.sol:IMD6900FrensValidatorForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 250.97µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenSwapper.fork.t.sol:FrenSwapperForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 368.47µs (0.00ns CPU time)\n\nRan 1 test for test/frens/IMD6900FrensPermit2.fork.t.sol:IMD6900FrensPermit2ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 413.63µs (0.00ns CPU time)\n\nRan 1 test for test/frens/FrenMinter.fork.t.sol:FrenMinterForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 643.08µs (0.00ns CPU time)\n\nRan 1 test for test/FrensPlacement.t.sol:FrensPlacementForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.43ms (0.00ns CPU time)\n\nRan 9 tests for test/frens/IMD6900FrensValidator.t.sol:IMD6900FrensValidatorTest\n[PASS] test_AuthorizationDoesNotOutliveTheSale() (gas: 193381)\n[PASS] test_AuthorizationFromTwoTopLevelCallsIsLost() (gas: 86911)\nLogs:\n  sale authorized in a separate top-level call: blocked\n\n[PASS] test_AuthorizationIsForThatFrenOnly() (gas: 394740)\n[PASS] test_AuthorizationIsForThatOperatorOnly() (gas: 479875)\n[PASS] test_ConduitAloneIsBlocked() (gas: 53311)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 79396)\n[PASS] test_HolderSendsItHerself() (gas: 87104)\n[PASS] test_OnlyTheAuthorizerAuthorizes() (gas: 74631)\n[PASS] test_ZoneAuthorizedSaleGoesThroughInOneFrame() (gas: 124660)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 4.21ms (682.17µs CPU time)\n\nRan 8 tests for test/FrensLaunchReview.t.sol:FrensAuditReviewTest\n[PASS] test_Audit_GovernorCanCloseMintAgain() (gas: 463703)\n[PASS] test_Audit_OwnerOrGovernorCanBlockAllPeerTransfers() (gas: 1372488)\n[PASS] test_Fix_LapsedJobApprovalIsReleasedByTheReveal() (gas: 1034129)\n[PASS] test_Fix_LastFrenOutStaysOutSoFeesHaveAnOwner() (gas: 1171597)\n[PASS] test_Fix_LaunchAdoptsASwapperSeededAtThePoolsPrice() (gas: 1868092)\n[PASS] test_Fix_LaunchRefusesTheWrongArt() (gas: 18811)\n[PASS] test_Fix_LaunchReplacesASwapperSeededOffASkewedPrice() (gas: 2596387)\n[PASS] test_Fix_LaunchsOwnSwapperStartsAtThePoolsPrice() (gas: 236724)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.60ms (4.20ms CPU time)\n\nRan 18 tests for test/frens/FrenWorkerGate.t.sol:FrenWorkerGateTest\n[PASS] test_ARefusedMintTakesNothing() (gas: 104998)\n[PASS] test_AnNftCountsOnce_whoeverHoldsItLater() (gas: 151129)\n[PASS] test_CreditsGoWhereTheHolderSays() (gas: 639709)\n[PASS] test_NoGateNoWindow() (gas: 442112)\n[PASS] test_OnlyFrensSpends() (gas: 32613)\n[PASS] test_OnlyTheHolderClaims() (gas: 67063)\n[PASS] test_OpenPublic() (gas: 528980)\n[PASS] test_OwnerMintsBeforeTheOpening() (gas: 2296674)\n[PASS] test_SeatOperatorClaimsTheStrategysNfts() (gas: 138514)\n[PASS] test_TheCreditIsTheMinters() (gas: 576408)\n[PASS] test_WindowClosesAfter420() (gas: 32491871)\n[PASS] test_WindowNeedsACreditPerFren() (gas: 714638)\n[PASS] test_WindowNeverOvershoots() (gas: 36197537)\n[PASS] test_WlAndWorkersShareThe420() (gas: 619421)\n[PASS] test_WlOnlyAsListed_once() (gas: 254028)\n[PASS] test_WlRaisedAmount_claimsTheDifference() (gas: 226858)\n[PASS] test_WlRootOnlyTheOwner_andNoRootNoWl() (gas: 62252)\n[PASS] test_WlWalletsMintInTheWindow_asMuchAsListed() (gas: 751030)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 14.26ms (9.76ms CPU time)\n\nRan 15 tests for test/FrensLaunchAdaptation.t.sol:FrensLaunchAdaptationTest\n[PASS] test_BootstrapThenOpenAndResume() (gas: 2316354)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\n[PASS] test_FirstFrensBeforeFeesPreventsPublicFloorCapture() (gas: 3361837)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_FirstFrensRequiresPausedBuysAndNonzeroCount() (gas: 1786016)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRefuseAnOwnerlessFloor() (gas: 1828998)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_OpenAndResumeRejectChangedWiring() (gas: 2613667)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_PlacedRejectsMissingOrWrongModules() (gas: 126553)\n[PASS] test_PlacedRejectsWrongCollectionAndForeignDependencies() (gas: 65517)\n[PASS] test_PlacedReportsTheLaunchModules() (gas: 51662)\n[PASS] test_ResumeRequiresDistributorAndActualSwapperExemption() (gas: 2165092)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_FirstMintCapturesExistingFloorIfRunbookIsBypassed() (gas: 2980745)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_LaunchBlockPushSeedsSwapperAndSetupRejectsAfterPriceReturns() (gas: 2428184)\n[PASS] test_Risk_SpentNonceStrandsJobAllowanceInPinnedCollection() (gas: 2351055)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_Risk_UnsupportedRoyaltyTokenHasNoFloorRoute() (gas: 3703404)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupPausesBeforeFirstMintEvenWhenAlreadyDistributor() (gas: 1815843)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n\n[PASS] test_SetupUsesTheLaunchsReplacementSwapper() (gas: 6693994)\nLogs:\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 set up, sealed, drawn by 0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n  floor buys paused: firstFrens(), then the timelock batch, then resume()\n  frens 0x69007Ce82E0BF7981780585afF7c597415903547 floor buys resumed\n\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 32.13ms (13.12ms CPU time)\n\nRan 6 tests for test/frens/FrenSwapperAverage.t.sol:FrenSwapperAverageTest\n[PASS] test_AFullBuyMovesItOneStep() (gas: 103298)\n[PASS] test_ASmallBuyMovesItInProportion() (gas: 52688)\n[PASS] test_DustMovesItNextToNothing() (gas: 7581882)\n[PASS] test_FloorRateIsTheLowerReading() (gas: 17216)\n[PASS] test_MovesDownToo() (gas: 52548)\n[PASS] test_OnceABlock() (gas: 91113)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 51.47ms (5.60ms CPU time)\n\nRan 84 tests for test/frens/IMD6900Frens.t.sol:IMD6900FrensTest\n[PASS] test_AMintNeverFailsOnTheSwap() (gas: 946501)\n[PASS] test_BatchCannotRepeatAFren() (gas: 641777)\n[PASS] test_BatchCountIsOneTo69() (gas: 2185636)\n[PASS] test_BatchCountsTheCapsAsItGoes() (gas: 11785283)\n[PASS] test_BatchHoldsPepesForLowTiers() (gas: 12180874)\n[PASS] test_BatchPaysOneJob() (gas: 700293)\n[PASS] test_BatchRevealIsExactlyTheVoucher() (gas: 1012095)\n[PASS] test_BatchRevealsThemAll() (gas: 2254191)\n[PASS] test_BatchSoldOut() (gas: 90765188)\n[PASS] test_BuyTreasuryRespectsMaxPay() (gas: 1807199)\n[PASS] test_BuyingFromTheTreasuryRaisesTheFloor() (gas: 3060520)\n[PASS] test_CurveWhileAboveTheFloor() (gas: 1591127)\n[PASS] test_DearerImd6900RaisesTheFloorPrice() (gas: 2846975)\n[PASS] test_DustEthBuyCannotTakeTheMintsTurn() (gas: 539958)\n[PASS] test_EveryFrenIsOneOfAKind() (gas: 947054)\n[PASS] test_FailedJob_payAnother() (gas: 1168121)\n[PASS] test_FeeEthBuysTheFloor() (gas: 1702668)\n[PASS] test_FloorBuysAreAnyones_onePerBlock() (gas: 1886993)\n[PASS] test_FloorBuysAreCapped() (gas: 55657779)\n[PASS] test_FloorIsReserveOverFrensOut() (gas: 2749300)\n[PASS] test_FloorMovesSkipTheValidator() (gas: 2110486)\n[PASS] test_FrenSentStraightToTheTreasuryCounts() (gas: 2605187)\n[PASS] test_IsACreatorToken() (gas: 41743)\n[PASS] test_JobPaymentIsPinnedToImdsPayee() (gas: 974626)\n[PASS] test_KeeperApprovesTheJobPaidFor() (gas: 653212)\n[PASS] test_KeeperCannotApproveLongLivedPayments() (gas: 434010)\n[PASS] test_LapsedPaymentIsUsedAgain() (gas: 715229)\n[PASS] test_LastFrenOutStaysOut() (gas: 1582490)\n[PASS] test_LaterPartsAgreeOnWhatIsRevealed() (gas: 1347460)\n[PASS] test_LowTierRequestsNeedAPepeLeft() (gas: 11712756)\n[PASS] test_MaxMintGrowsWithTheBag() (gas: 4682103)\n[PASS] test_MaxPayStopsAPriceThatMoved() (gas: 10066140)\n[PASS] test_MaxRequest() (gas: 9919282)\n[PASS] test_MinTierOnlyGoesDown() (gas: 138825)\n[PASS] test_MintMintsAtOnce_unrevealed() (gas: 770570)\n[PASS] test_MintNeedsSealedTraits() (gas: 8627562)\n[PASS] test_MintNeverBelowTheFloor() (gas: 3403449)\n[PASS] test_MintNeverSkipsTheBuyForWantOfGas() (gas: 708859)\n[PASS] test_NoMintIntoTheTreasury() (gas: 1590016)\n[PASS] test_NoRequestForNobody() (gas: 61934)\n[PASS] test_NoTierInsideAFlashLoan() (gas: 687603)\n[PASS] test_NobodyElseApprovesJobs() (gas: 435602)\n[PASS] test_OneRevealPerFren() (gas: 717815)\n[PASS] test_OnlyHoldersRecycle() (gas: 1587638)\n[PASS] test_OnlyRealFrens() (gas: 743528)\n[PASS] test_OnlyTheOwnerSetsTheValidator() (gas: 1060220)\n[PASS] test_OwnerAdjustsTheArtUntilFrozen() (gas: 1552272)\n[PASS] test_PairRules() (gas: 875609)\n[PASS] test_PartsOnlyGoForward() (gas: 1035574)\n[PASS] test_PriceRisesWithMints() (gas: 38121727)\n[PASS] test_PriceTableIsTheCurve() (gas: 37031661)\n[PASS] test_QuoteCountsUnsweptImd() (gas: 3192315)\n[PASS] test_RareTraitsNeedTheTier() (gas: 1366007)\n[PASS] test_RecyclePaysTheFloorAndKeepsIt() (gas: 2892194)\n[PASS] test_RecyclePaysTheWaitingImdToo() (gas: 2719969)\n[PASS] test_ReleaseLapsedJobAfterTheReveal() (gas: 2133093)\n[PASS] test_RequestMintFor() (gas: 1084049)\n[PASS] test_ReserveOnlyLeavesThroughRecycle() (gas: 2826030)\n[PASS] test_RevealFollowsTheFren() (gas: 749295)\n[PASS] test_RevealInParts() (gas: 9744736)\nLogs:\n  gas, minting 69: 2117876\n  gas, revealing 30: 3092889\n\n[PASS] test_RevealNeedsTheRelayersVoucher() (gas: 803728)\n[PASS] test_RevealReleasesALapsedPayment() (gas: 916235)\n[PASS] test_RevealShowsTheFren() (gas: 1189238)\n[PASS] test_RevealsWhenever() (gas: 661569)\n[PASS] test_RoyaltiesPayTheFloor() (gas: 1718930)\n[PASS] test_RoyaltyBpsIsCappedAndOwnerOnly() (gas: 109478)\n[PASS] test_RulesAreFrozenOnceSealed() (gas: 69602)\n[PASS] test_SealNeedsEveryTraitAndTheSupply() (gas: 10522996)\n[PASS] test_SetMaxMint() (gas: 178348)\n[PASS] test_ShortSwapReverts() (gas: 1043815)\n[PASS] test_SwapperMayPullOnlyTheBuy() (gas: 954501)\n[PASS] test_TheOwnerKeepsTheCollection_theGovernorTheMechanics() (gas: 488972)\n[PASS] test_TierCountsWhatIsLeftAfterPaying() (gas: 919870)\n[PASS] test_TierIsTheBestOfTheBag() (gas: 633637)\n[PASS] test_TierIsTheMints() (gas: 1350662)\n[PASS] test_TierThresholdsOwnerOnlyAndOrdered() (gas: 155016)\n[PASS] test_TradesGoPastTheValidator() (gas: 1343397)\n[PASS] test_TraitCapsHold() (gas: 25774957)\n[PASS] test_TreasuryRoundTripCostsTheFloor() (gas: 3239545)\n[PASS] test_UnneededRetryFeedsTheFloor() (gas: 1023226)\n[PASS] test_UnrevealedFrenSellsAtTheFloor() (gas: 1015894)\n[PASS] test_UnrevealedFrensShareTheFloor() (gas: 1451826)\n[PASS] test_UnusedJobFeedsTheFloor() (gas: 697059)\n[PASS] test_VouchersExpire() (gas: 472525)\nSuite result: ok. 84 passed; 0 failed; 0 skipped; finished in 51.49ms (193.72ms CPU time)\n\nRan 3 tests for test/FrensLaunchReview.t.sol:FrensFactoryReviewTest\n[PASS] test_Audit_ConstructorLeavesTheDocumentedTeamSetup() (gas: 25152531)\n[PASS] test_FreshChainCallsOnlyContractsCreatedByTheLaunch() (gas: 28519889)\n[PASS] test_ProtectedFactoryDeploysBothLaunchesInOrder() (gas: 25351455)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 69.28ms (13.43ms CPU time)\n\nRan 12 tests for test/FrensArtFraming.t.sol:FrensArtFramingTest\n[PASS] testFuzz_EntryPastTheIndexIsMissing(uint256) (runs: 256, μ: 13984, ~: 13943)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] testFuzz_UnknownBackgroundIsMissing(uint256) (runs: 256, μ: 35613, ~: 35592)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_ATamperedChunkDrawsNothing() (gas: 90530)\n[PASS] test_AttributesDoNotValidate() (gas: 369298)\n[PASS] test_ChunksAreFramedPushData() (gas: 2731712)\n[PASS] test_EveryValueHasAName() (gas: 15679313)\n[PASS] test_IndexHashesAreTheChunks() (gas: 788314)\n[PASS] test_IndexPointsInsideTheChunks() (gas: 525933)\n[PASS] test_LaunchLayersAreWellFormed() (gas: 46186857)\n[PASS] test_NoSuchFrenIsMissing() (gas: 4375022)\n[PASS] test_PalettesFitTogether() (gas: 13989817)\n[PASS] test_WithoutTheSwarmNothingDraws() (gas: 5947823)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 69.28ms (227.41ms CPU time)\n\nRan 19 tests for test/FrensLaunchFailures.t.sol:FrensLaunchFailuresTest\n[PASS] testFuzz_CheckKnowsAFren(uint24,uint8) (runs: 512, μ: 1934229, ~: 1907720)\nLogs:\n  Bound result 3\n\n[PASS] testFuzz_OtherSaltsLandElsewhere(bytes32) (runs: 256, μ: 34945, ~: 34945)\n[PASS] testFuzz_QuoteIsTheCurveBeforeAnyoneHolds(uint256) (runs: 512, μ: 328887, ~: 326243)\nLogs:\n  Bound result 19\n\n[PASS] test_AnotherOwnerMovesTheFrens() (gas: 34214)\n[PASS] test_CollectionRefusesAShortPriceTable() (gas: 3059925)\n[PASS] test_CollectionWaitsForTheTeamWallet() (gas: 5941920)\n[PASS] test_EveryPriceIsTheTables() (gas: 27629816)\n[PASS] test_FreshChainLaunchesAreIndependent() (gas: 1351085)\n[PASS] test_LaunchContractsTakeNoEth() (gas: 687657)\n[PASS] test_ModulesFailOverCorruptedFrens() (gas: 97101)\n[PASS] test_ModulesNeedTheCollection() (gas: 106270)\n[PASS] test_NoLaunchActorHoldsARole() (gas: 4583570)\n[PASS] test_NoPricePastTheTable() (gas: 41307)\n[PASS] test_OtherSaltSchemeFailsTheLaunch() (gas: 140656)\n[PASS] test_RefusingDeployerFailsTheLaunch() (gas: 103369)\n[PASS] test_ScriptRefusesAnUnplacedChain() (gas: 14619610)\n[PASS] test_SecondLaunchReusesWhatIsPlaced() (gas: 1313470)\n[PASS] test_SilentDeployerFailsTheLaunch() (gas: 93234)\n[PASS] test_WrongArtFailsTheArtLaunch() (gas: 588910)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 70.41ms (236.36ms CPU time)\n\nRan 13 tests for test/FrensPlacement.t.sol:FrensPlacementTest\n[PASS] test_Attributes() (gas: 19594305)\n[PASS] test_CodeIsWhatTheSourcesBuild() (gas: 88334)\n[PASS] test_DeploysOnAFreshChain() (gas: 51532495)\n[PASS] test_EachLaunchFitsOneTransaction() (gas: 105289625)\nLogs:\n  the collection launch: gas, all in: 12308310\n  the art launch: gas, all in: 13605058\n\n[PASS] test_LandsWhereThePlanSays() (gas: 51643509)\n[PASS] test_NewArtReadsBackExactly() (gas: 14173584)\n[PASS] test_PassesTheAdmissionScan() (gas: 87697392)\n[PASS] test_PlanFollowsFromTheCode() (gas: 79539)\n[PASS] test_PlanMatchesTheScript() (gas: 14147485)\n[PASS] test_PriceTableIsTheCurve() (gas: 52824571)\n[PASS] test_RefusesOtherArt() (gas: 12971045)\n[PASS] test_SameAddressesWhoeverRunsIt() (gas: 102213411)\n[PASS] test_TakesWhatSomeonePlacedFirst() (gas: 52211127)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 87.79ms (171.83ms CPU time)\n\nRan 1 test for test/FrensSplitGas.t.sol:FrensSplitGasTest\n[PASS] test_CollectionFitsButLegacyFourContractBundleExceedsCap() (gas: 100924258)\nLogs:\n  legacy four-contract launch: 23723070\n  two-contract collection launch: 13340309\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 106.41ms (106.26ms CPU time)\n\nRan 1 test for test/FrensPlacedInvariants.t.sol:FrensPlacedInvariantsTest\n[PASS]\nFrensPlacedInvariantsTest invariants:\n[PASS] invariant_CapsHold\n[PASS] invariant_FloorIsAShareOfTheWorld\n[PASS] invariant_FrensAreConserved\n[PASS] invariant_ImdIsTheBooks\n[PASS] invariant_ReserveIsExactlyWhatIsHere\n FrensPlacedInvariantsTest invariants (runs: 32, calls: 1920, reverts: 2)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| PlacedFrensHandler | approveJob      | 116   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyFloor        | 109   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | buyTreasury     | 125   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donate6900      | 139   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | donateImd       | 104   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | feeEth          | 112   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mint            | 255   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | mintThenRecycle | 114   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | recycle         | 116   | 2       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | retryJob        | 133   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | reveal          | 126   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setParams       | 132   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | setRate         | 105   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | transfer        | 113   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| PlacedFrensHandler | warp            | 121   | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3923\n  Bound result 1000000000000000000000000\n  Bound result 4315\n  Bound result 0\n  Bound result 16246\n  Bound result 1819\n  Bound result 3\n  Bound result 999997\n  Bound result 39\n  Bound result 66\n  Bound result 19\n  Bound result 33\n  Bound result 1\n  Bound result 1\n  Bound result 4467\n  Bound result 12\n  Bound result 0\n  Bound result 17670\n  Bound result 190000000000000000\n  Bound result 38\n  Bound result 15\n  Bound result 9165\n  Bound result 2\n  Bound result 72\n  Bound result 5422346600000000000000\n  Bound result 1\n  Bound result 671072\n  Bound result 69\n  Bound result 1001\n  Bound result 0\n  Bound result 8321\n  Bound result 561\n  Bound result 821056\n  Bound result 96\n  Bound result 112\n  Bound result 3235\n  Bound result 34\n  Bound result 2\n  Bound result 14450\n  Bound result 25\n  Bound result 16\n  Bound result 2\n  Bound result 76\n  Bound result 16618\n  Bound result 192\n  Bound result 1\n  Bound result 64\n  Bound result 98\n  Bound result 30\n  Bound result 750\n  Bound result 1115\n  Bound result 25\n  Bound result 0\n  Bound result 173891586404266843082\n  Bound result 499999999999998001\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.64s (2.64s CPU time)\n\nRan 17 test suites in 2.64s (3.21s CPU time): 189 tests passed, 0 failed, 5 skipped (194 total tests)\n","passed":true},{"durationMs":101,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrenMinter.buyImd(uint256)\",\"FrenMinter.mintWithEth(uint8,uint256)\",\"FrenMinter.quoteEth(uint8)\",\"FrenMinter.quoteFloor(uint256)\",\"FrenMinter.quoteFloorEth(uint256)\",\"FrenMinter.unlockCallback(bytes)\",\"FrenSwapper.ethToImd6900(uint256,address)\",\"FrenSwapper.imdToImd6900(uint256,uint256,address)\",\"FrenSwapper.unlockCallback(bytes)\",\"FrenWorkerGate.cancelOwnershipHandover()\",\"FrenWorkerGate.claim(uint256[],address)\",\"FrenWorkerGate.claimWl(uint256,bytes32[],address)\",\"FrenWorkerGate.completeOwnershipHandover(address)\",\"FrenWorkerGate.openPublic()\",\"FrenWorkerGate.renounceOwnership()\",\"FrenWorkerGate.requestOwnershipHandover()\",\"FrenWorkerGate.setWlRoot(bytes32)\",\"FrenWorkerGate.spend(address,uint256)\",\"FrenWorkerGate.transferOwnership(address)\",\"IMD6900Frens.addPairRule((uint8,uint8,uint8,uint8,uint8))\",\"IMD6900Frens.approve(address,uint256)\",\"IMD6900Frens.approveJob(uint256,uint256,uint256,(string,bytes32,string,bytes32,bytes32,string,uint256))\",\"IMD6900Frens.buyFloor(uint256)\",\"IMD6900Frens.buyFloorWithEth(uint256,uint256)\",\"IMD6900Frens.buyTreasury(uint256,uint256,uint256)\",\"IMD6900Frens.cancelOwnershipHandover()\",\"IMD6900Frens.completeOwnershipHandover(address)\",\"IMD6900Frens.freezeArt()\",\"IMD6900Frens.lowerMinTier(uint8,uint8,uint8)\",\"IMD6900Frens.receive()\",\"IMD6900Frens.recycle(uint256)\",\"IMD6900Frens.releaseLapsedJob(uint256)\",\"IMD6900Frens.renounceOwnership()\",\"IMD6900Frens.requestMint(uint8,uint256)\",\"IMD6900Frens.requestMintFor(address,uint8,uint256)\",\"IMD6900Frens.requestOwnershipHandover()\",\"IMD6900Frens.retryJob(uint256)\",\"IMD6900Frens.reveal(uint256,uint24[],string,bytes32,uint256,bytes,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256)\",\"IMD6900Frens.safeTransferFrom(address,address,uint256,bytes)\",\"IMD6900Frens.sealTraits()\",\"IMD6900Frens.setApprovalForAll(address,bool)\",\"IMD6900Frens.setGovernor(address)\",\"IMD6900Frens.setMaxMint(uint8[4])\",\"IMD6900Frens.setMintOpen(bool)\",\"IMD6900Frens.setModules(address,address)\",\"IMD6900Frens.setParams(uint256,uint256,uint256)\",\"IMD6900Frens.setRenderer(address)\",\"IMD6900Frens.setRoles(address,address,address)\",\"IMD6900Frens.setRoyalty(uint96)\",\"IMD6900Frens.setTiers(uint256[3],uint256[3],uint256[3])\",\"IMD6900Frens.setTraitRules(uint8,uint16[],uint8[])\",\"IMD6900Frens.setTransferValidator(address)\",\"IMD6900Frens.transferFrom(address,address,uint256)\",\"IMD6900Frens.transferOwnership(address)\",\"IMD6900Frens.unwrapWeth()\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":145,\"README.md\":181,\"foundry.toml\":22,\"launch.json\":16,\"script/art/chunks.py\":165,\"script/art/data/bgpal/cleanlab_blue.bin\":1,\"script/art/data/bgpal/cleanlab_green.bin\":1,\"script/art/data/bgpal/cleanlab_red.bin\":1,\"script/art/data/bgpal/messylab_blue.bin\":1,\"script/art/data/bgpal/messylab_green.bin\":1,\"script/art/data/bgpal/messylab_red.bin\":1,\"script/art/data/bgpal/tubeblue.bin\":1,\"script/art/data/bgpal/tubegreen.bin\":2,\"script/art/data/bgpal/tubered.bin\":1,\"script/art/data/bgpal/tubeyellow.bin\":1,\"script/art/data/bgpal/wireframe_green.bin\":2,\"script/art/data/bgpal/wireframe_red.bin\":1,\"script/art/data/expected.json\":53,\"script/art/data/layers/bg0.bin\":9,\"script/art/data/layers/bg1.bin\":9,\"script/art/data/layers/bg10.bin\":32,\"script/art/data/layers/bg11.bin\":32,\"script/art/data/layers/bg2.bin\":9,\"script/art/data/layers/bg3.bin\":15,\"script/art/data/layers/bg4.bin\":15,\"script/art/data/layers/bg5.bin\":15,\"script/art/data/layers/bg6.bin\":26,\"script/art/data/layers/bg7.bin\":26,\"script/art/data/layers/bg8.bin\":26,\"script/art/data/layers/bg9.bin\":26,\"script/art/data/layers/coat.bin\":20,\"script/art/data/layers/item5.bin\":2,\"script/art/data/manifest.json\":500,\"script/art/data/shared.bin\":4,\"script/art/data/tables.bin\":2,\"script/art/swarm-v2.json\":374,\"script/frens/DeployFrens.s.sol\":243,\"script/frens/FrensTimelockBatch.s.sol\":105,\"script/frens/price/prices-swarm.bin\":6,\"script/frens/price/prices.bin\":6,\"script/frens/price/prices.json\":26,\"script/frens/price/prices.py\":55,\"script/frens/price/prices_sol.py\":33,\"script/placement/addresses.json\":7,\"script/placement/gen.py\":135,\"src/FrensCode.sol\":15,\"src/FrensPlacement.sol\":129,\"src/FrensPlan.sol\":37,\"src/art/WorkerArt.sol\":671,\"src/art/WorkerArtIndex.sol\":56,\"src/frens/FrenMinter.sol\":194,\"src/frens/FrenPrices.sol\":122,\"src/frens/FrenSwapper.sol\":249,\"src/frens/FrenWorkerGate.sol\":137,\"src/frens/IMD6900Frens.sol\":1072,\"src/frens/WorkerFrensRenderer.sol\":377,\"test/FrensArtFraming.t.sol\":290,\"test/FrensLaunchAdaptation.t.sol\":317,\"test/FrensLaunchFailures.t.sol\":526,\"test/FrensLaunchReview.t.sol\":449,\"test/FrensPlacedInvariants.t.sol\":619,\"test/FrensPlacement.t.sol\":818,\"test/FrensSplitGas.t.sol\":68,\"test/frens/DataWriter.sol\":21,\"test/frens/FrenMinter.fork.t.sol\":189,\"test/frens/FrenSwapper.fork.t.sol\":134,\"test/frens/FrenSwapperAverage.t.sol\":77,\"test/frens/FrenWorkerGate.t.sol\":350,\"test/frens/FrensRules.sol\":75,\"test/frens/IMD6900Frens.t.sol\":1710,\"test/frens/IMD6900FrensPermit2.fork.t.sol\":130,\"test/frens/IMD6900FrensValidator.fork.t.sol\":146,\"test/frens/IMD6900FrensValidator.t.sol\":231,\"test/test_collection_manifest.py\":28},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e062878ea43a049ae5cb4833ea92052dec464781a9843b410a93df589cd83a91","verifiedTreeHash":"88411ef34421358f6909a628828ef7661b2d6e37","verifierVersion":"0.1.0+ad90ce4c"}]}