{"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":"703944b1-8946-4995-945a-3f61749f8545","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"34ee873dc20da2a42346c565a12dfb984d16443b0fdbba4dd9c5e26f58232237","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f954c8d37aea737c297075b42c1821983edea9bb9af911832c97a12793ed3177","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"601cdcfbc01482d1ddb522db780a079cc0af24d45ec9a28b7b889076df04da7c","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"27727e8e8b5a6056d4f8de382543d10e0d6501d55f1b0e7820308477cc7909dc","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"b0ae12b6095643c45a21781639ed139ad7dfc4466a4cb5f2d585bf7aec14e91e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"0fa7f73a28b44b4dfd83f974ad5cb7bfed1987681d3bf11c9b2ebf37161dd51b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"8aa676f4b6276183090b9074f49e61856491661e03e61fc44ddc85a87342ff0d","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"ed0dac235254dab95a0ed1a13e109f6b9bed8bbea6cb28799b70493643e2156e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Hash Frog: browser-mined PoW NFT + HFROG launch token. Launch kind univ4_hook on Ethereum mainnet, paired with IMD: the factory's standard token and pool with our hook, plus our NFT (with burn vault) and staking contracts. Deploy in the launch, then host a site on the live addresses. No owner, admin, upgrade or pause in our contracts.\n\nNFT \"Hash Frog\" (HASHFROG)\n- Cap 2,000 ever minted; burned ids never reused.\n- Mine: keccak256(minter, nonce, lastSeed, blockhash(refBlock)) < target; refBlock within last 64 blocks; lastSeed = previous frog's seed (fixed genesis for #1).\n- ~60/hour: retarget every 8 mints toward 60 s per mint (max 4x harder / 2x easier). Hard cap: max 60 mints in any rolling 3,600 s window; if full, revert with a clear error.\n- One per tx, price exactly 0.0019 ETH. Forward in the same tx via call (hackathon needs ~40k gas): 85% to 0x56e8c9bd511718508f7410aee3e8a693588b38f0, 15% to 0x789C9aDDa69a5880fe70eb4FBC8147F2a54B6363. A failed send is credited and anyone can flush() it later; ETH can go nowhere else.\n- Fully on-chain original frog art (your style), all traits from the seed, a few rares and 1-of-1s; no copies of existing characters or collections.\n\nToken \"HFROG\" (HFROG): the launch's standard token, as the launch deploys it.\n- Our hook adds 1.5% on buys and 1.5% on sells, taken in IMD, on top of the launch's pool fee. Of every hook fee: 60% to HFROG stakers, 25% to the frog vault, 15% to 0x789C9aDDa69a5880fe70eb4FBC8147F2a54B6363. No anti-snipe.\n- Frog vault: holds IMD from the hook. buyback(): anyone, at most once per 60 s and at most 100 IMD per call, swaps vault IMD into HFROG through the pool; the HFROG stays in the vault.\n\nBurn: a frog holder burns a frog and receives vault HFROG / frogs not yet burned (and the same share of any unswapped IMD). The vault pays out only through burns; nobody can withdraw it.\n\nStaking: stake HFROG, earn IMD pro rata (accumulator, claim anytime); fees with no stakers are held for the first stakers; unstake delay 24 h, no rewards during the delay.\n\nSite: browser miner (WebGPU, WASM/CPU fallback; hashrate, difficulty, ETA, free slots this hour), mint, gallery from tokenURI, buy/sell HFROG vs IMD, burn (shows what one frog burns into now), buyback, stake/unstake/claim, stats (minted/2,000, vault balance, ETH to hackathon and team, IMD to stakers).\n\nTests: <=2,000 mints; <=60 per 3,600 s; solution bound to minter; reused lastSeed fails; wrong price reverts; 85/15 exact, flush works; burn pays exactly its pro-rata share, vault never negative, no withdrawal; buyback limits hold; hook 1.5% each way split 60/25/15; staking pays out what the hook paid in; nothing moves funds elsewhere. Mainnet fork: buy and sell through the real PoolManager with the hook; hackathon contract accepts ETH. Independent review.","parentJobId":null,"planHash":"0fd9de284fc5d6d68e81d93baa98f388b754113152ab1ca8db4eb9bd8fbe8cb5","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"703944b1-8946-4995-945a-3f61749f8545","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-892-hash-frog-browser-mined-pow"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50974","feedbackHash":"7bcf1325fa4003ac01e876607588c4d15a320e341dac89e4bddbf81889b2e532","nodeKey":"audit_economics","submissionHash":"34ee873dc20da2a42346c565a12dfb984d16443b0fdbba4dd9c5e26f58232237","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52292","feedbackHash":"79f23867aa65616cc99a82943cff98fe190aa41248c33e357347422db23f9ce1","nodeKey":"audit_flow","submissionHash":"f954c8d37aea737c297075b42c1821983edea9bb9af911832c97a12793ed3177","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51081","feedbackHash":"064561e69fcb98874940fe8fee5e0fc8b4412da8fbab2c1d5b0657e0dbabe171","nodeKey":"audit_judge","submissionHash":"601cdcfbc01482d1ddb522db780a079cc0af24d45ec9a28b7b889076df04da7c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50939","feedbackHash":"9e108f2f0b0cdb1332860e1a43c88e0b56732579c563c023e20e173d5e2a0512","nodeKey":"audit_math","submissionHash":"27727e8e8b5a6056d4f8de382543d10e0d6501d55f1b0e7820308477cc7909dc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51023","feedbackHash":"d07dcc5ca7e65c499eaaf601ea7176d80b6a00a9cf05d6dcb908218f31a0913b","nodeKey":"audit_permissions","submissionHash":"b0ae12b6095643c45a21781639ed139ad7dfc4466a4cb5f2d585bf7aec14e91e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51188","feedbackHash":"931a31f1871748b5975f897b54e131d39dc92ed9b068a1e3c919a08d11cd203c","nodeKey":"build_contract_project","submissionHash":"0fa7f73a28b44b4dfd83f974ad5cb7bfed1987681d3bf11c9b2ebf37161dd51b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51200","feedbackHash":"6ba2863b53bed7b60fe85bd930fc2b9e7e452e2a91da1299842ace548d7bcfa4","nodeKey":"manifest","submissionHash":"8aa676f4b6276183090b9074f49e61856491661e03e61fc44ddc85a87342ff0d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52184","feedbackHash":"00cd97a8d900b3b42f24f292666da89b7255b718e5cd2feebf1b8a934afc7f07","nodeKey":"write_foundry_tests","submissionHash":"ed0dac235254dab95a0ed1a13e109f6b9bed8bbea6cb28799b70493643e2156e","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"2a5c2e0ff42b61e8f9109c7f246e9fac98629a076076197e05a24c6242d51c92","state":"completed","submissions":[{"artifacts":[],"attempt":3,"bundleHash":"52995d086980bc908c1bd0a3f748f0ade0046e3e5cf874920e16fed0979f36e2","device":"0b0761c9b10fe0bb","findings":[],"hash":"0fa7f73a28b44b4dfd83f974ad5cb7bfed1987681d3bf11c9b2ebf37161dd51b","nodeId":"776bd72a-f8e5-4937-ac8e-627586aeb9a9","outcome":"completed","summary":"Implemented contracts, on-chain art, browser mining/site, tests, and launch documentation.\n\nVerified: `forge build`, 47 tests, `forge fmt --check`, all 10 supplied protected checks, and local frontend integration.\n\nMainnet deployment, hosting, real-input fork rehearsal, and independent review remain outstanding. Missing inputs and operational steps are documented in [README.md](/root/.identitymd/work/703944b1-8946-4995-945a-3f61749f8545/776bd72a-f8e5-4937-ac8e-627586aeb9a9/README.md).","treeHash":"bec15f5ea5c12d5bec35f610f707678d53a43703","usage":{"cachedInputTokens":4508416,"inputTokens":163441,"model":"gpt-6-astra","outputTokens":84357,"runtime":"codex","turns":9,"wallClockMs":3207428}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"0fce87924985c9ba","findings":[],"hash":"20517522f0a9529a88f2915375a8c0662912e016763cf37f36a2205a734f791e","nodeId":"776bd72a-f8e5-4937-ac8e-627586aeb9a9","outcome":"failed","summary":"Your workspace is out of credits. Ask your workspace owner to refill in order to continue.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":5,"wallClockMs":1699816}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"72b617d4b615473a","findings":[{"citation":"resolved","description":"Boundary x invariant seam. A position has one exit queue (p.exiting, p.unlockAt). Every requestUnstake overwrites p.unlockAt for the whole queue, including HFROG queued earlier whose 24 h had almost elapsed. The brief's guarantee is an unstake delay of 24 h; here the first tranche's delay becomes up to 48 h minus one second, and during that time it earns no rewards either (active was already reduced). The staker's own call triggers it, so there is no third-party griefing, but the guarantee 'unstake delay 24 h' does not hold per tranche. Minimal fix that keeps the design: either make a second request while one is pending revert, or keep unlockAt as max(existing, now+24h) only for the newly added amount by tracking tranches, or let unstake() release the earlier tranche at its original time.","line":99,"path":"src/FrogStaking.sol","reproduction":"Reproduced with test/scratch/Edges.t.sol::testExitRestartLocksEarlierTranche on the Fixture (warp 1_000_000). stake(100e18); requestUnstake(50e18) at t=1_000_000 -> unlockAt=1_086_400. warp to 1_086_399 and requestUnstake(50e18): position becomes exiting=100e18, unlockAt=1_172_799. At t=1_086_400 (the first tranche's original unlock) unstake() reverts TooEarly(1_172_799). Expected: the first 50e18 withdrawable at 1_086_400; actual: withdrawable only at 1_172_799, 86_399 s later.","severity":"low","snippet":"        p.unlockAt = block.timestamp + UNSTAKE_DELAY;","title":"requestUnstake restarts the 24 h delay for HFROG already queued, so an earlier tranche can wait up to 48 h"},{"citation":"resolved","description":"Precision x invariant seam (two formulas that should agree use different bases). For an exact-input buy, beforeSwap (line 124) charges fee = gross * 150 / 10000, i.e. 1.5% of what the buyer spends. For an exact-output buy, afterSwap line 150 charges fee = poolInput * 150 / 10000, where poolInput is net of the fee, so fee / (poolInput + fee) = 0.015 / 1.015 = 1.4778% of what the buyer spends. The sell side is internally consistent (exact-output sells gross up with 9850 so both sell paths are 1.5% of gross output); the buy side is not. A buyer who always submits exact-output orders pays 2.2 bps less than the brief's 1.5%, and stakers/vault/team receive proportionally less. If the intended definition is 1.5% of IMD spent, line 150 should gross up with 9850 for the exact-output-buy case (imdDelta < 0), mirroring line 124. The README table documents the current behaviour, so this may be an accepted definition; it is reported because the brief states a single 1.5% on buys.","line":150,"path":"src/HashFrogHook.sol","reproduction":"Reproduced with test/scratch/FeeBasis.t.sol::testExactOutputBuyFeeBasis on the Fixture pool (seedPool, 1:1 price). Exact-output buy of 100e18 HFROG: buyer's IMD delta = 102_785_837_984_962_128_103 (gross), hook fee = 1_519_002_531_797_469_873 = 1.4778% of gross. Exact-input buy spending the same gross 102_785_837_984_962_128_103 IMD: hook fee = 1_541_787_569_774_431_921 = 1.4999% of gross. Same IMD spent, fee differs by 22_785_037_976_962_048 wei (2.2 bps of gross). Expected per brief: both 1.5% of the buyer's IMD.","severity":"low","snippet":"            fee = amount * FEE_BPS / 10000;","title":"Exact-output buys pay 1.478% of IMD spent, exact-input buys pay 1.500%: the two buy paths use different fee bases"},{"citation":"resolved","description":"Boundary x precision. fee = amount * 150 / 10000 is 0 for amount <= 66 wei, and _accrue(0) returns early. The pool's 1.25% LP fee still applies in-pool and the gas cost of a swap is many orders of magnitude above 1 wei, so splitting a trade into <67-wei legs can never recover the 1.5% economically. No action needed beyond noting that the 1.5% is a floor-rounded fee in IMD minor units, which the README already states.","line":124,"path":"src/HashFrogHook.sol","reproduction":"test/scratch/Edges.t.sol::testTinySwapPaysNoHookFee: router.swap(true, 66, 1, 0, deadline) on the seeded Fixture pool: spent=66, bought=64, hook.totalFees()=0. Expected under exact arithmetic: 0.99 wei fee; actual: 0.","severity":"info","snippet":"            fee = amount * FEE_BPS / (params.amountSpecified < 0 ? 10000 : 9850);","title":"Hook fee truncates to zero for IMD legs below 67 wei"},{"citation":"resolved","description":"Boundary/saturation note on the retarget math (documented by the author as 'Target stays in [1, 2^256-1]' and covered by testRetargetHarderEasierAndSaturation). From INITIAL_TARGET = 2^236, each epoch of 8 mints that takes >= 960 s doubles the target; after 20 such epochs (160 mints spread over at least 5.3 hours) target = type(uint256).max, and the check uint256(seed) >= target rejects only the single digest 2^256-1. From then on mining requires no work at all and the only limiter is the 60-per-3600 s window and gas, until 4x-harder epochs (each needing 8 mints in < 120 s) raise it again: 10 epochs / 80 mints to return to 2^236. The arithmetic itself is correct (the overflow guard on line 114 is right and mulDiv cannot revert); this is a design consequence of the brief's 2x-easier clamp with no lower bound on difficulty, reported so the author can confirm it is intended for a browser-mined collection.","line":115,"path":"src/HashFrog.sol","reproduction":"test/NFT.t.sol::testRetargetHarderEasierAndSaturation already drives target to type(uint256).max from 1<<248 in two 2x epochs with elapsed > 960 s. test/scratch/FeeBasis.t.sol::testSaturatedTargetAcceptsNonceZero shows keccak(alice, 0, lastSeed, blockhash) < type(uint256).max, i.e. nonce 0 passes once saturated. Expected under a 'proof of work' reading: some minimum work always required; actual: zero work after saturation.","severity":"info","snippet":"            ? type(uint256).max","title":"Retarget has no difficulty floor: after 20 slow epochs the target saturates at type(uint256).max and every nonce is a valid proof"}],"hash":"27727e8e8b5a6056d4f8de382543d10e0d6501d55f1b0e7820308477cc7909dc","nodeId":"c88ab47a-9985-448d-ae22-f856d449a627","outcome":"completed","summary":"Review complete. The findings file is written and validated; the math of this project holds up well, with no critical or high defects in my area.\n\n**What I produced**\n\n`.imd-findings.json` at the repository root, with 4 findings and 30 coverage rows (all 29 listed entry points plus one oracle invariant row). Every snippet was checked programmatically against the cited file and line, with zero mismatches. Scratch reproductions live under `test/scratch/` (FeeBasis.t.sol, Edges.t.sol); both pass and print the numbers quoted in the findings. The full project suite was run first: 47 tests pass.\n\n**Findings**\n\n- **Low, FrogStaking.requestUnstake:** a second request overwrites the single exit timer, so HFROG queued 24 h minus 1 s earlier waits almost 48 h total. Reproduced: first tranche unlock moves from 1,086,400 to 1,172,799.\n- **Low, HashFrogHook.afterSwap:** exact-output buys charge 1.5% of the pool input, which is 1.478% of the IMD the buyer spends, while exact-input buys charge 1.500% of spend. Reproduced on the fixture: same 102.79 IMD gross, fees differ by 0.0228 IMD. Sells are consistent on both paths.\n- **Info:** hook fee truncates to zero below 67 wei of IMD. Economically irrelevant, noted for completeness.\n- **Info:** the retarget has no difficulty floor. After 20 slow epochs the target saturates at the maximum and any nonce is a valid proof until 4x-harder epochs rebuild it. The author documents and tests this clamp.\n\n**What I verified as holding**\n\nMint price split is exact, the 60-slot ring correctly bounds the rolling hour, retarget clamps and overflow guard are right, flush cannot underflow, burn shares are floor pro-rata with dust to the last frog, the staking accumulator cannot overflow for any plausible IMD supply and keeps its remainder, the hook's four swap modes balance deltas and the 60/25/15 split sums exactly, and the oracle's int192 accumulation, interpolation and tick rounding are sound.\n\n**Not done**\n\nNo proofs were attached since nothing reached high severity. The protected admission suite was read but not executed, because it needs environment variables the task does not supply. Economic manipulation of the 30-minute TWAP and access-control questions are outside my assigned guides and were only sanity-checked, not audited in depth.","treeHash":null,"usage":{"cachedInputTokens":1262991,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":43109,"runtime":"claude","turns":25,"wallClockMs":848000}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"98b4506bef931d13","findings":[{"citation":"resolved","description":"buyback() is permissionless and the caller chooses `amount` anywhere in (0, 100 IMD]. Any successful call sets `lastBuyback = block.timestamp`, so the smallest amount that clears the 95% TWAP floor (about 1,000 wei of IMD at a 1:1 price, since the floor is 0 below about 20 wei) locks out every real buyback for 60 s. Repeating it once a minute, or front-running each keeper call, limits the vault to dust buybacks, and the vault's IMD never converts to HFROG. The brief's 'at most 100 IMD per call, once per 60 s' was meant as a throughput limit for the vault, but the cooldown currently costs nothing to occupy. Economic Security guide: 'Find the cheapest griefing vector that blocks other users.' The griefer pays only gas (about 0.5M gas per call in the test, cold), and funds are not lost: burns still pay the unswapped IMD. What breaks is the buyback guarantee.\nseam: execution x first-principles. Fix that keeps the spec: ignore the caller's amount and spend `min(MAX_BUYBACK, available IMD)`, with minOut checked against the floor for that amount. Or require `amount >= min(MAX_BUYBACK, available)`.","line":184,"path":"src/HashFrog.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {HFROG} from \"src/HFROG.sol\";\nimport {HashFrogHook} from \"src/HashFrogHook.sol\";\nimport {HashFrog} from \"src/HashFrog.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"IdentityMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// A dust buyback (1,000 wei of IMD) consumes the 60 s cooldown, so the vault's 100 IMD buyback\n/// for that minute cannot run. Repeating it every minute caps buybacks at dust.\ncontract BuybackGriefTest is Test {\n    HFROG token;\n    PairToken imd;\n    PoolManager manager;\n    HashFrogHook hook;\n    HashFrog frog;\n    address griefer = makeAddr(\"griefer\");\n    address keeper = makeAddr(\"keeper\");\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        vm.roll(100);\n        manager = new PoolManager(address(this));\n        token = new HFROG();\n        imd = new PairToken();\n        bytes memory creation =\n            abi.encodePacked(type(HashFrogHook).creationCode, abi.encode(manager, token, imd, int24(60)));\n        bytes32 hash = keccak256(creation);\n        address at;\n        for (uint256 i; i < 200_000; ++i) {\n            at = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(at) & 0x3fff != 0x20cc) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") { at := create2(0, add(creation, 32), mload(creation), salt) }\n            break;\n        }\n        require(at.code.length > 0, \"no salt\");\n        hook = HashFrogHook(at);\n        frog = hook.frog();\n        PoolKey memory key = hook.poolKey();\n        manager.initialize(key, 1 << 96);\n        PoolModifyLiquidityTest liquidity = new PoolModifyLiquidityTest(manager);\n        token.approve(address(liquidity), type(uint256).max);\n        imd.approve(address(liquidity), type(uint256).max);\n        liquidity.modifyLiquidity(key, ModifyLiquidityParams(-60000, 60000, 10_000_000 ether, bytes32(0)), \"\");\n        // Vault holds 1,000 IMD of hook fees (donation is equivalent for burn/buyback accounting).\n        imd.transfer(address(frog), 1000 ether);\n        vm.warp(vm.getBlockTimestamp() + 1801); // oracle warm-up\n    }\n\n    function _floor(uint256 amount) internal view returns (uint256) {\n        return hook.quoteAtTick(hook.consult(), uint128(amount), address(imd) < address(token)) * 95 / 100;\n    }\n\n    function testDustBuybackCannotBlockFullBuybackForTheMinute() public {\n        uint256 deadline = vm.getBlockTimestamp();\n        // Griefer front-runs the keeper with the smallest buyback that clears the floor.\n        vm.prank(griefer);\n        try frog.buyback(1000, _floor(1000), 0, deadline) {} catch {}\n        // Keeper's legitimate 100 IMD buyback in the same minute.\n        vm.prank(keeper);\n        try frog.buyback(100 ether, _floor(100 ether), 0, deadline) {} catch {}\n        // Spec: up to 100 IMD per 60 s is swapped. Actual: 1,000 wei.\n        assertGe(frog.totalBuybackIMD(), 100 ether, \"cooldown consumed by dust buyback\");\n    }\n}","reproduction":"Pool initialized at 1:1 with 10M liquidity in [-60000, 60000], vault holds 1,000 IMD, oracle warmed for 1,801 s. In the same block: (1) griefer calls frog.buyback(1000, 950, 0, now) and it succeeds: totalBuybackIMD = 1000 wei, lastBuyback = now. (2) keeper calls frog.buyback(100e18, 95e18, 0, now), which reverts BuybackCooldown(now+60). Expected: up to 100 IMD swapped this minute. Actual: 1,000 wei. Repeating step (1) every 60 s keeps this going indefinitely. The proof test fails today with 'cooldown consumed by dust buyback: 1000 < 1e20'.","severity":"medium","snippet":"        if (amount == 0 || amount > MAX_BUYBACK) revert InvalidBuybackAmount();\n        if (block.timestamp < lastBuyback + 60) revert BuybackCooldown(lastBuyback + 60);","title":"Anyone can block the vault's 100 IMD/min buyback with a dust buyback that consumes the 60 s cooldown"},{"citation":"resolved","description":"The vault collects 25% of every hook fee from the first swap after launch. Frogs mint no faster than 60/h, so at least 34 h pass before all 2,000 exist. burnQuote divides by minted-minus-burned, which makes a frog's claim fall each time someone else mints. A new frog costs 0.0019 ETH, all of which goes to the hackathon and team and none to the vault. Yet it is immediately worth vault/(alive+1) and can be burned in the next transaction. Each such mint-and-burn moves vault/(alive+1) from existing holders to the newcomer. Whenever vault/(alive+1) exceeds 0.0019 ETH plus gas, a mint-and-burn loop drains the vault, capped only by the 60/h mint limit. In the state alive == 0 (every minted frog burned), the next minter gets the whole vault. The brief divides by 'frogs not yet burned' under a 2,000 cap with burned ids never reused. Read as 2,000 - totalBurned, each burn is exactly pro-rata, the per-frog claim never decreases, and value accrued before sellout stays for the frogs not yet minted. The README flags the minted-minus-burned reading as one the requester 'must accept before launch'. It has not been accepted, and the economics differ materially. Invariant guide: 'Break commutativity' / 'first/last participant' and 'Abuse boundaries'.\ninvariant: an existing frog's burn value must not fall when another account mints and burns.","line":162,"path":"src/HashFrog.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {HFROG} from \"src/HFROG.sol\";\nimport {HashFrog} from \"src/HashFrog.sol\";\nimport {FrogRouter} from \"src/FrogRouter.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"IdentityMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// Production HashFrog plus two test-only knobs: trivial difficulty and a reset of the\n/// one-mint-per-transaction latch (Foundry runs a whole test in one transaction).\ncontract FrogHarness is HashFrog {\n    constructor(IPoolManager m, IERC20 t, IERC20 i) HashFrog(m, t, i, FrogRouter(address(0)), address(0)) {}\n\n    function nextTransaction() external {\n        bytes32 slot = MINT_TX_SLOT;\n        assembly (\"memory-safe\") { tstore(slot, 0) }\n    }\n\n    function easy() external {\n        target = type(uint256).max;\n    }\n}\n\n/// The burn denominator is minted-minus-burned, so every new 0.0019 ETH mint takes a full share of a\n/// vault that existing frogs already back, and can be burned at once. The mint price goes to the\n/// hackathon/team, not the vault: value moves from existing holders to the newcomer.\ncontract BurnDilutionTest is Test {\n    FrogHarness frog;\n    PairToken imd;\n    HFROG hfrog;\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        vm.roll(100);\n        hfrog = new HFROG();\n        imd = new PairToken();\n        frog = new FrogHarness(new PoolManager(address(this)), hfrog, imd);\n        frog.easy();\n        vm.deal(alice, 1 ether);\n        vm.deal(bob, 1 ether);\n    }\n\n    function _mint(address who) internal returns (uint256 id) {\n        frog.nextTransaction();\n        bytes32 seed = frog.lastSeed();\n        vm.prank(who);\n        id = frog.mine{value: 0.0019 ether}(0, seed, 99);\n    }\n\n    function testNewMintAndImmediateBurnDilutesExistingHolder() public {\n        _mint(alice); // frog #1\n        // 1,000 IMD of hook fees (25% share) and 1,000,000 HFROG of buybacks reach the vault.\n        imd.transfer(address(frog), 1000 ether);\n        hfrog.transfer(address(frog), 1_000_000 ether);\n        (uint256 aliceHfrogBefore, uint256 aliceImdBefore) = frog.burnQuote();\n        // Today aliceImdBefore == 1,000 IMD: the single live frog can take the entire vault.\n\n        // Bob mines #2 (0.0019 ETH goes to hackathon/team, nothing to the vault) and burns it at once.\n        uint256 id = _mint(bob);\n        vm.prank(bob);\n        frog.burn(id); // today: 500 IMD + 500,000 HFROG for a 0.0019 ETH mint\n\n        (uint256 aliceHfrogAfter, uint256 aliceImdAfter) = frog.burnQuote();\n        // Alice's claim was halved by a newcomer who contributed nothing to the vault.\n        assertGe(aliceImdAfter, aliceImdBefore, \"existing frog's IMD claim diluted by mint+burn\");\n        assertGe(aliceHfrogAfter, aliceHfrogBefore, \"existing frog's HFROG claim diluted by mint+burn\");\n    }\n}","reproduction":"Alice mines frog #1 (alive = 1). The vault receives 1,000 IMD of fees and 1,000,000 HFROG of buybacks. burnQuote() returns (1,000,000 HFROG, 1,000 IMD) for Alice. Bob mines #2 for 0.0019 ETH (0.001615 to the hackathon, 0.000285 to the team, 0 to the vault) and calls burn(2) in the next transaction. He receives 500 IMD + 500,000 HFROG. Alice's burnQuote is now (500,000 HFROG, 500 IMD): she lost half her claim to a newcomer who added nothing to the vault. With denominator MAX_SUPPLY - totalBurned, Bob would receive 0.5 IMD + 500 HFROG and Alice's quote would stay at 0.5 IMD + 500 HFROG. The proof test fails today with 'existing frog's IMD claim diluted by mint+burn: 5e20 < 1e21'.","severity":"medium","snippet":"        uint256 alive = liveSupply();\n        if (alive == 0) return (0, 0);\n        return (hfrog.balanceOf(address(this)) / alive, (imd.balanceOf(address(this)) + pendingFees()) / alive);","title":"Burn denominator = minted - burned lets each new 0.0019 ETH mint take a full share of the existing vault and burn it at once, diluting current holders"},{"citation":"resolved","description":"For buys, the fee basis depends on the swap mode. Exact-input buys charge 1.5% of the gross IMD budget (line 124, `fee = amount * FEE_BPS / (params.amountSpecified < 0 ? 10000 : 9850);`), which is 1.5228% of what enters the pool. Exact-output buys charge 1.5% of the pool's IMD input (line 150), which is 1.4778% of what the buyer pays. Sells are consistent: both modes charge 1.5% of gross pool output. So 'the same 1.5% buy fee' differs by about 1.5% of the fee depending on the router, and any aggregator quoting exact-output pays stakers, the vault and the team less. Fix: on the unspecified-IMD buy path (exact output, IMD input), gross up the same way the exact-output sell path does: `fee = amount * 150 / 9850`.","line":150,"path":"src/HashFrogHook.sol","reproduction":"Pool 1:1 with 10M liquidity in [-60000, 60000]. Exact-output buy of 1,000 HFROG: the buyer pays 1,027.950896 IMD, of which the hook fee is 15.191393 IMD (1.4778%). Exact-input buy with the same 1,027.950896 IMD budget: hook fee 15.419263 IMD (1.5000%), 999.775 HFROG out. For the same IMD, the exact-output buyer receives 0.0225% more HFROG and fee recipients receive 0.228 IMD (1.48%) less. Measured with the repository Fixture (trade(true,false,1000e18) against trade(true,true,paid)).","severity":"low","snippet":"            fee = amount * FEE_BPS / 10000;","title":"Exact-output buys pay the 1.5% hook fee on the pool input, not on the buyer's gross IMD, so they underpay compared with exact-input buys"},{"citation":"resolved","description":"While totalStaked == 0, every staker share is added to queuedRewards. The next stake() calls _allocate(0), which divides the whole queue by totalStaked at that moment, so a 1-wei stake takes all of it. At launch, the queue fills with 60% of the fees from the launch-window volume before anyone can hold HFROG, and the first buyer able to stake (typically a sniper) takes all of it with dust. The same happens whenever every staker has requested unstake and totalStaked returns to 0. The brief says the queue is 'held for the first stakers' (plural). The README calls this by design, but a dust staker capturing the whole queue does not hold it for stakers in any useful sense. Fix that keeps the spec: release the queue over a window (for example add queuedRewards/N per notify, or stream it over 24 h) instead of in one allocation.","line":59,"path":"src/FrogStaking.sol","reproduction":"Fresh deployment, pool seeded, no stakers. Buys totalling 1,000,000 IMD give 15,000 IMD of fees and queuedRewards = 9,000 IMD. The attacker calls stake(1) (1 wei HFROG), so earned(attacker) = 9,000 IMD. Bob then stakes 1,000,000 HFROG and has earned(bob) = 0 from the queue. The attacker calls claim() and receives 9,000 IMD for a 1-wei stake. The existing test testEmptyRewardsGoToFirstStakerAndExitStopsEarnings shows the same capture with a 100-token stake (0.9 IMD queue).","severity":"low","snippet":"        if (totalStaked == 0) {\n            queuedRewards += amount;","title":"Fees queued while nobody stakes go entirely to the first staker, even one staking 1 wei"}],"hash":"34ee873dc20da2a42346c565a12dfb984d16443b0fdbba4dd9c5e26f58232237","nodeId":"f54f543a-4417-43c2-b662-5152ec0713ff","outcome":"completed","summary":"I found two medium and two low issues in the economics, all in `.imd-findings.json` with coverage for all 29 entry points plus 2 invariants. The two mediums each have a self-contained Foundry proof that fails on the current code for the stated reason. No source files changed: my scratch tests were deleted, and the only leftovers are the findings file and the ignored `out/` and `cache/` build folders.\n\n**Findings**\n\n1. **Medium: anyone can block the vault's buyback cheaply** (`src/HashFrog.sol:184`). The caller picks the amount, so a 1,000-wei buyback passes the price floor and starts the 60 s cooldown. A 100 IMD buyback in the same minute then reverts with `BuybackCooldown`. Repeated every minute, buybacks stay at dust for the cost of gas only. Burns still pay out the unswapped IMD, so nothing is lost, but the 100-IMD-per-minute buyback never happens. Fix within the spec: always spend `min(100 IMD, vault balance)`, ignoring the caller's amount.\n2. **Medium: new mints dilute existing frog holders** (`src/HashFrog.sol:162`). A burn pays vault ÷ (minted − burned). A new frog costs 0.0019 ETH, none of which reaches the vault, yet it can be burned at once for an equal share. In the proof, a second minter takes 500 of 1,000 IMD (and half the HFROG) and the first holder's claim halves. If every minted frog has been burned, the next minter gets the whole vault. Dividing by 2,000 − burned, which the brief's wording also allows, keeps each holder's claim from falling. The README says the requester must accept this choice before launch; I found no record that they have.\n3. **Low: exact-output buys pay a smaller hook fee** (`src/HashFrogHook.sol:150`). They pay 1.4778% of what the buyer spends, against 1.5% for exact-input buys with the same IMD. Measured: a fee of 15.19 IMD versus 15.42 IMD on a 1,027.95 IMD buy. Fix: gross it up by 9850, as the exact-output sell path already does.\n4. **Low: a 1-wei stake takes all queued staking rewards** (`src/FrogStaking.sol:59`). Fees collected while nobody stakes all go to the next stake, however small. This happens at launch, and again whenever every staker has requested unstake. The README calls this intended, but the brief says \"first stakers\", plural.\n\n**Checked and holding:**\n- The hook's fee deltas balance in both pool currency orderings, the 60/25/15 split is right, and the fee claims are backed by the PoolManager's IMD.\n- The 85/15 ETH split adds up to the full price, and `flush()` can't be re-entered. The live hackathon contract on mainnet needs about 18.4k gas to receive, well inside the 100k allowed.\n- Mint limits, the price-average buyback floor and staking accounting behave as specified.\n- I rejected the static-analysis leads on the oracle's \"weak PRNG\" and on re-entrancy in `flush()` as false positives.\n\nI left out the ability to stake just before a large swap to catch its fees, since the 24-hour unstake delay is the intended protection.","treeHash":null,"usage":{"cachedInputTokens":2150799,"inputTokens":42,"model":"claude-opus-5-5","outputTokens":47313,"runtime":"claude","turns":26,"wallClockMs":748864}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2c968e88904ec22b","findings":[{"citation":"resolved","description":"buyback() is permissionless and the caller picks any amount in (0, 100 IMD]. Every successful call sets lastBuyback = block.timestamp (line 189), so a call for the smallest amount that clears the 95% TWAP floor (about 1,000 wei of IMD at a 1:1 price; 2 wei is rejected because the pool output rounds to zero) locks out every other buyback for 60 s. A griefer who repeats this each minute, or front-runs each keeper call, limits the vault to dust buybacks indefinitely; vault IMD then never converts to HFROG. No funds are lost (burns still pay the unswapped IMD share) and the griefer pays gas every minute with no direct gain, so this is a griefing vector rather than a loss: low. Merged from audit_economics (reported medium; recalibrated because no guarantee of buyback throughput is stated and nothing is lost). Spec-preserving fix: make the cooldown apply only to a full-size buyback, e.g. require amount == min(MAX_BUYBACK, redeemed vault IMD), or ignore the caller's amount and always spend that minimum, computing the floor for that amount.","line":185,"path":"src/HashFrog.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {HFROG} from \"src/HFROG.sol\";\nimport {HashFrogHook} from \"src/HashFrogHook.sol\";\nimport {HashFrog} from \"src/HashFrog.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"IdentityMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// A dust buyback (1,000 wei of IMD) consumes the 60 s cooldown, so the vault's 100 IMD buyback\n/// for that minute cannot run. Repeating it every minute caps buybacks at dust.\ncontract BuybackGriefTest is Test {\n    HFROG token;\n    PairToken imd;\n    PoolManager manager;\n    HashFrogHook hook;\n    HashFrog frog;\n    address griefer = makeAddr(\"griefer\");\n    address keeper = makeAddr(\"keeper\");\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        vm.roll(100);\n        manager = new PoolManager(address(this));\n        token = new HFROG();\n        imd = new PairToken();\n        bytes memory creation =\n            abi.encodePacked(type(HashFrogHook).creationCode, abi.encode(manager, token, imd, int24(60)));\n        bytes32 hash = keccak256(creation);\n        address at;\n        for (uint256 i; i < 200_000; ++i) {\n            at = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(at) & 0x3fff != 0x20cc) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") { at := create2(0, add(creation, 32), mload(creation), salt) }\n            break;\n        }\n        require(at.code.length > 0, \"no salt\");\n        hook = HashFrogHook(at);\n        frog = hook.frog();\n        PoolKey memory key = hook.poolKey();\n        manager.initialize(key, 1 << 96);\n        PoolModifyLiquidityTest liquidity = new PoolModifyLiquidityTest(manager);\n        token.approve(address(liquidity), type(uint256).max);\n        imd.approve(address(liquidity), type(uint256).max);\n        liquidity.modifyLiquidity(key, ModifyLiquidityParams(-60000, 60000, 10_000_000 ether, bytes32(0)), \"\");\n        // Vault holds 1,000 IMD of hook fees (donation is equivalent for burn/buyback accounting).\n        imd.transfer(address(frog), 1000 ether);\n        vm.warp(vm.getBlockTimestamp() + 1801); // oracle warm-up\n    }\n\n    function _floor(uint256 amount) internal view returns (uint256) {\n        return hook.quoteAtTick(hook.consult(), uint128(amount), address(imd) < address(token)) * 95 / 100;\n    }\n\n    function testDustBuybackCannotBlockFullBuybackForTheMinute() public {\n        uint256 deadline = vm.getBlockTimestamp();\n        // Griefer front-runs the keeper with the smallest buyback that clears the floor.\n        vm.prank(griefer);\n        try frog.buyback(1000, _floor(1000), 0, deadline) {} catch {}\n        // Keeper's legitimate 100 IMD buyback in the same minute.\n        vm.prank(keeper);\n        try frog.buyback(100 ether, _floor(100 ether), 0, deadline) {} catch {}\n        // Spec: up to 100 IMD per 60 s is swapped. Actual: 1,000 wei.\n        assertGe(frog.totalBuybackIMD(), 100 ether, \"cooldown consumed by dust buyback\");\n    }\n}","reproduction":"Fixture: fresh PoolManager, pool at 1:1 with 10M liquidity in [-60000, 60000], vault holds 1,000 IMD, oracle warmed 1,801 s. Same block: (1) griefer calls frog.buyback(1000, floor(1000)=950, 0, now) and it succeeds: totalBuybackIMD = 1000 wei, lastBuyback = now. (2) keeper calls frog.buyback(100e18, 95e18, 0, now): reverts BuybackCooldown(now + 60). Expected: up to 100 IMD swapped this minute. Actual: 1,000 wei. Reproduced with the attached proof, which fails on this tree with 'cooldown consumed by dust buyback: 1000 < 100000000000000000000'. A 2-wei attempt (test/scratch probe) reverts inside the swap because the pool output rounds to 0, so ~1,000 wei is the practical minimum.","severity":"low","snippet":"        if (block.timestamp < lastBuyback + 60) revert BuybackCooldown(lastBuyback + 60);","title":"A dust buyback occupies the 60 s cooldown, so anyone can cap the vault's buybacks at dust for the cost of gas"},{"citation":"resolved","description":"The brief states one 1.5% buy fee. Exact-input buys (beforeSwap line 124, divisor 10000) take 1.5% of the gross IMD the buyer spends. Exact-output sells (beforeSwap, divisor 9850) gross up so the fee is 1.5% of the gross IMD the pool pays out, and exact-input sells take 1.5% of that gross output. Only the exact-output buy branch in afterSwap applies 150/10000 to `amount`, which there is the pool's IMD input before the fee is added on top; the buyer pays amount + fee, so the fee is 150/10150 = 1.4778% of what they spend, 2.2 bps below the exact-input buy rate. FrogRouter and the site only send exact-input orders, so the path is reached by external routers (Universal Router exact-output buys); stakers, vault and team collect about 1.5% less fee on such trades, and a router can pick the cheaper mode. Merged from audit_economics, audit_flow, audit_math and audit_permissions (all low; same root cause). The README table documents the current formula, so it may be an accepted definition; reported because the brief gives a single rate for buys. Minimal fix: when imdDelta < 0 in afterSwap use fee = amount * FEE_BPS / (10000 - FEE_BPS), mirroring the exact-output sell gross-up.","line":150,"path":"src/HashFrogHook.sol","reproduction":"Fixture with seedPool() (1:1, 10M liquidity). trade(true, false, 100e18) (exact-output buy of 100 HFROG): buyer's IMD delta = 102,785,837,984,962,128,103 wei gross; hook.totalFees() = 1,519,002,531,797,469,873 wei = 14,778 ppm of gross. Then trade(true, true, 102,785,837,984,962,128,103) (exact-input buy of the same gross): fee = 1,541,787,569,774,431,921 wei = 14,999 ppm of gross. Expected: both buy modes charge 1.5% of the IMD the buyer pays. Actual: 1.4778% vs 1.5%. Reproduced in test/scratch/Probe.t.sol::testExactOutputBuyFeeBasis (not kept).","severity":"low","snippet":"            fee = amount * FEE_BPS / 10000;","title":"Exact-output buys charge 1.5% of the pool's IMD input, not of the buyer's gross IMD, so the two buy modes charge different rates"},{"citation":"resolved","description":"While totalStaked == 0 every staker share accrues to queuedRewards. stake() checkpoints the caller at the old accumulator, adds the new amount to totalStaked and calls _allocate(0), which divides the entire queue by totalStaked at that moment, i.e. only the new staker. There is no minimum stake and claim() has no delay, so a 1-wei stake followed by claim() in the next transaction takes the whole queue; the same happens whenever every staker has requested exit and totalStaked returns to 0. The brief says no-staker fees are 'held for the first stakers' (plural); the README states the single-first-stake capture is by design. Reported so the requester confirms that reading, since the asymmetry (1 wei risked, 24 h lock, whole launch-window staker share received) means a bot rather than the early staker cohort receives it. Merged from audit_economics and audit_permissions (both low). If unwanted, release the queue over time (stream it after the first stake) or require a minimum first stake; both change economics and need the requester's decision.","line":62,"path":"src/FrogStaking.sol","reproduction":"Fixture with seedPool(), nobody staked. trade(true,true,10_000e18) then trade(false,true,10_000e18): staking.queuedRewards() = 178,960,045,795,707,695,373 wei (178.96 IMD). token.transfer(alice, 1); alice approves and calls staking.stake(1): staking.earned(alice) = 178,960,045,795,707,695,373; alice.claim() returns exactly that amount in the same block. bob then stakes 1,000,000 HFROG: earned(bob) = 0. Expected under 'held for the first stakers': shared by the early staker cohort pro rata; actual: 100% to a 1-wei stake. Reproduced in test/scratch/Probe.t.sol::testOneWeiFirstStakerCapturesQueue (not kept); test/Staking.t.sol::testEmptyRewardsGoToFirstStakerAndExitStopsEarnings shows the same with 100 tokens.","severity":"low","snippet":"            uint256 scaled = (amount + queuedRewards) * PRECISION + scaledRemainder;","title":"Fees queued while nobody stakes are paid in full to whoever stakes first, even a 1-wei stake"},{"citation":"resolved","description":"burnQuote() divides the vault by liveSupply() = totalMinted - totalBurned. The vault collects 25% of every hook fee from the first swap, while frogs mint at most 60/hour, so a frog's claim falls each time someone else mints. The mint price goes entirely to the hackathon and team, not the vault, yet a new frog is immediately worth vault/(alive+1) and can be burned in the next transaction; when that exceeds 0.0019 ETH plus gas, a mint-and-burn loop moves vault value from existing holders to miners at up to 60/hour, and with alive == 0 the next minter takes the whole vault. The brief says 'vault HFROG / frogs not yet burned', which the implementation satisfies under the natural reading (a frog that has not been minted is not a frog), and the README and manifest notes state this denominator explicitly and say it 'must be accepted before launch'. This is therefore a design decision rather than a code defect, recorded as low (from audit_economics' medium) so the requester settles it: the alternative MAX_SUPPLY - totalBurned keeps every live frog's claim non-decreasing and reserves accrued value for unminted frogs, at the cost of locking value if the collection never sells out. If the requester confirms the current denominator this finding is closed by acceptance and the attached specialist proof (which encodes the alternative) is moot.","line":164,"path":"src/HashFrog.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {HFROG} from \"src/HFROG.sol\";\nimport {HashFrog} from \"src/HashFrog.sol\";\nimport {FrogRouter} from \"src/FrogRouter.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"IdentityMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// Production HashFrog plus two test-only knobs: trivial difficulty and a reset of the\n/// one-mint-per-transaction latch (Foundry runs a whole test in one transaction).\ncontract FrogHarness is HashFrog {\n    constructor(IPoolManager m, IERC20 t, IERC20 i) HashFrog(m, t, i, FrogRouter(address(0)), address(0)) {}\n\n    function nextTransaction() external {\n        bytes32 slot = MINT_TX_SLOT;\n        assembly (\"memory-safe\") { tstore(slot, 0) }\n    }\n\n    function easy() external {\n        target = type(uint256).max;\n    }\n}\n\n/// The burn denominator is minted-minus-burned, so every new 0.0019 ETH mint takes a full share of a\n/// vault that existing frogs already back, and can be burned at once. The mint price goes to the\n/// hackathon/team, not the vault: value moves from existing holders to the newcomer.\ncontract BurnDilutionTest is Test {\n    FrogHarness frog;\n    PairToken imd;\n    HFROG hfrog;\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        vm.roll(100);\n        hfrog = new HFROG();\n        imd = new PairToken();\n        frog = new FrogHarness(new PoolManager(address(this)), hfrog, imd);\n        frog.easy();\n        vm.deal(alice, 1 ether);\n        vm.deal(bob, 1 ether);\n    }\n\n    function _mint(address who) internal returns (uint256 id) {\n        frog.nextTransaction();\n        bytes32 seed = frog.lastSeed();\n        vm.prank(who);\n        id = frog.mine{value: 0.0019 ether}(0, seed, 99);\n    }\n\n    function testNewMintAndImmediateBurnDilutesExistingHolder() public {\n        _mint(alice); // frog #1\n        // 1,000 IMD of hook fees (25% share) and 1,000,000 HFROG of buybacks reach the vault.\n        imd.transfer(address(frog), 1000 ether);\n        hfrog.transfer(address(frog), 1_000_000 ether);\n        (uint256 aliceHfrogBefore, uint256 aliceImdBefore) = frog.burnQuote();\n        // Today aliceImdBefore == 1,000 IMD: the single live frog can take the entire vault.\n\n        // Bob mines #2 (0.0019 ETH goes to hackathon/team, nothing to the vault) and burns it at once.\n        uint256 id = _mint(bob);\n        vm.prank(bob);\n        frog.burn(id); // today: 500 IMD + 500,000 HFROG for a 0.0019 ETH mint\n\n        (uint256 aliceHfrogAfter, uint256 aliceImdAfter) = frog.burnQuote();\n        // Alice's claim was halved by a newcomer who contributed nothing to the vault.\n        assertGe(aliceImdAfter, aliceImdBefore, \"existing frog's IMD claim diluted by mint+burn\");\n        assertGe(aliceHfrogAfter, aliceHfrogBefore, \"existing frog's HFROG claim diluted by mint+burn\");\n    }\n}","reproduction":"FrogHarness (production HashFrog with trivial difficulty). Alice mines #1 (alive = 1). 1,000 IMD and 1,000,000 HFROG reach the vault. burnQuote() = (1,000,000 HFROG, 1,000 IMD). Bob mines #2 for 0.0019 ETH (0.001615 to hackathon, 0.000285 to team, 0 to vault) and calls burn(2) in the next transaction: receives 500 IMD + 500,000 HFROG. Alice's burnQuote() is now (500,000 HFROG, 500 IMD). With denominator MAX_SUPPLY - totalBurned Bob would receive 0.5 IMD + 500 HFROG and Alice's quote would stay 0.5 IMD + 500 HFROG. The attached proof fails on this tree with 'existing frog's IMD claim diluted by mint+burn: 500000000000000000000 < 1000000000000000000000'.","severity":"low","snippet":"        return (hfrog.balanceOf(address(this)) / alive, (imd.balanceOf(address(this)) + pendingFees()) / alive);","title":"Burn denominator is minted-minus-burned: each new 0.0019 ETH mint takes a full share of the existing vault and can burn it at once, diluting current holders"},{"citation":"resolved","description":"A position has one exit queue (p.exiting, p.unlockAt). Every requestUnstake overwrites unlockAt for the whole queue, including HFROG queued earlier whose 24 h had nearly elapsed, and that principal earns nothing meanwhile. Only the staker's own call triggers it, the README, manifest notes and test testPartialExitAndAdditionalExitRestartsDelay document it, and nothing is lost, so this is informational from audit_math's low. If the per-tranche 24 h guarantee matters, either revert a second request while one is pending or track tranches.","line":99,"path":"src/FrogStaking.sol","reproduction":"Fixture (warp 1_000_000). stake(100e18); requestUnstake(50e18) -> unlockAt = 1_086_400. warp 1_086_399; requestUnstake(50e18) -> exiting = 100e18, unlockAt = 1_172_799. warp 1_086_400; unstake() reverts TooEarly(1_172_799). Expected: first 50e18 withdrawable at 1_086_400; actual: 86,399 s later. Reproduced in test/scratch/Probe.t.sol::testExitRestartLocksEarlierTranche (not kept).","severity":"info","snippet":"        p.unlockAt = block.timestamp + UNSTAKE_DELAY;","title":"A second requestUnstake restarts the 24 h delay for HFROG already queued, so an earlier tranche can wait up to 48 h"},{"citation":"resolved","description":"From INITIAL_TARGET = 2^236 - 1, every 8-mint epoch that takes at least 960 s doubles the target; after 20 such epochs (160 mints over at least 5.3 h, e.g. a lull in interest) target = type(uint256).max and uint256(seed) >= target rejects only one digest in 2^256, so nonce 0 is a valid proof for anyone. From then on only the 60-per-3,600 s window and the 0.0019 ETH price limit mints; 4x-harder epochs (8 mints in under 120 s) need 10 epochs (80 mints, at least 80 min under the hourly cap) to return to 2^236. The arithmetic is correct (the overflow guard on line 114 is right) and the README states 'Target stays in [1, 2^256-1]'; reported from audit_math's info so the author confirms a temporarily work-free mint after lulls is acceptable for a 'browser-mined PoW' collection, or adds a floor (e.g. never above INITIAL_TARGET).","line":115,"path":"src/HashFrog.sol","reproduction":"test/NFT.t.sol::testRetargetHarderEasierAndSaturation drives target from 1<<248 to type(uint256).max in two epochs with elapsed > 960 s. With target == type(uint256).max, keccak256(abi.encodePacked(alice, uint256(0), lastSeed, blockhash(99))) < target holds (test/scratch/Probe.t.sol::testSaturatedTargetNeedsNoWork), so mine(0, lastSeed, refBlock) succeeds with zero hashing. Expected under a PoW reading: some minimum work; actual: none after saturation.","severity":"info","snippet":"            ? type(uint256).max","title":"Retarget has no difficulty floor: after 20 consecutive slow epochs the target saturates at type(uint256).max and mining needs no work until ~80 free mints raise it again"}],"hash":"601cdcfbc01482d1ddb522db780a079cc0af24d45ec9a28b7b889076df04da7c","nodeId":"9d423f97-c720-4c27-b10c-66d968c32291","outcome":"completed","summary":"The review is complete. The only new file in the tree is `.imd-findings.json`; the scratch tests are removed and `git status` shows nothing else changed.\n\n**Outcome: no critical, high or medium defects.** The code holds on every entry point I traced, including on a real mainnet fork. I kept six findings, four low and two informational, all reproduced against this tree.\n\n**What I verified beyond the specialists' reports**\n\n- The full suite passes (68 tests, 1 fork test skipped offline).\n- I ran the project's own mainnet fork rehearsal through a public RPC at block 26140180 against the real PoolManager and the real IMD token. Buy, sell, stake, claim, mint, hackathon ETH acceptance (no failed-send credit), redeem and burn all pass. The brief's \"mainnet fork\" test requirement is therefore met, not just scripted.\n- The IMD address in launch.json has code on mainnet, 18 decimals and symbol \"IMD\", so beforeInitialize will accept it. The manifest's constructor arguments, permissions, fee, spacing and initial price match the implementation, and the notes fit the schema limit.\n- Hook initcode is 37,916 bytes, under the EIP-3860 limit, and runtime code has no escape opcodes.\n- I traced the v4 delta accounting in the vendored Hooks library: the claims-minting design keeps the hook's net delta at zero on all four swap modes and needs no manager balance.\n\n**Findings kept, with severity recalibrated**\n\n1. Low: a ~1,000 wei buyback occupies the 60 s cooldown, so anyone can cap vault buybacks at dust for gas. Downgraded from the economics specialist's medium because nothing is lost. The specialist's proof fails on this tree and is attached.\n2. Low: exact-output buys charge 1.478% of gross IMD while the other three modes charge 1.5%. Four specialists reported this; merged into one.\n3. Low: a 1-wei first stake captures the entire no-staker reward queue. Two specialists; merged. Documented by the author, so the requester must confirm or change it.\n4. Low: the burn denominator is minted-minus-burned, so a new mint immediately dilutes holders and a mint-and-burn loop can extract vault value. Downgraded from medium: the brief's wording supports the implementation and the README flags it for acceptance. The specialist's proof is attached but is moot if the requester accepts the design.\n5. Info: a second requestUnstake restarts the 24 h delay for earlier queued HFROG.\n6. Info: with no difficulty floor, 20 slow epochs saturate the target and mining needs no work until ~80 free mints.\n\n**Dropped:** the sub-67-wei fee truncation (normal floor rounding, not a defect) and the two \"$imd placeholder\" notes, which no longer apply since launch.json carries the literal IMD address.\n\nThe coverage record answers all 29 entry points plus four invariant rows (fork rehearsal, manifest, code size, static-analysis leads). The static-analysis leads all resolved as non-defects after tracing.","treeHash":null,"usage":{"cachedInputTokens":1416535,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":60939,"runtime":"claude","turns":40,"wallClockMs":1108728}},{"artifacts":[],"attempt":1,"bundleHash":"1d1d0eab03636936abb8dc7346c4edd3ec7f7327d7769625e185b7f515cdd54f","device":"2468e053049c5c80","findings":[],"hash":"8aa676f4b6276183090b9074f49e61856491661e03e61fc44ddc85a87342ff0d","nodeId":"18a8ac1b-40c6-47ba-b865-a63eb59c0c8e","outcome":"completed","summary":"Created [launch.json](/home/seat/.identitymd/work/703944b1-8946-4995-945a-3f61749f8545/18a8ac1b-40c6-47ba-b865-a63eb59c0c8e/launch.json) matching the contracts, permissions, IMD pair, and required fee. Notes document the 1:1 initial-price assumption and pending review, mainnet rehearsal, deployment, and hosting.\n\nSchema constraints and compiled constructor ABI checks passed. `forge build` succeeded; all 47 tests passed. Only `launch.json` changed.","treeHash":"1c5b029854bb95d161f48845c9dfb3c97c82cdef","usage":{"cachedInputTokens":420352,"inputTokens":55116,"model":"gpt-6-astra","outputTokens":5182,"runtime":"codex","turns":4,"wallClockMs":196060}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ca080fd306399669","findings":[{"citation":"resolved","description":"While totalStaked == 0 every hook fee's 60% staker share accumulates in queuedRewards. stake() checkpoints the caller, adds the new amount to totalStaked, and then calls _allocate(0), which pays the entire queue to the current staker set, which at that moment is only the new staker, regardless of how small the stake is. There is no minimum stake, no vesting of the queue, and claim() has no delay, so the first staker (an MEV bot watching queuedRewards grow at launch, or whenever every staker has requested exit so totalStaked returns to 0) stakes 1 wei of HFROG, claims the whole queue in the next transaction, and requests exit. The brief says fees with no stakers are 'held for the first stakers'; the README states this capture is by design. It is reported here because the asymmetry between what is risked (1 wei, locked 24 h) and what is received (the full launch-window staker share) means the intended cohort of early stakers receives nothing, and the trigger needs no privilege. Treat as a design confirmation for the author; if unwanted, release the queue over time (e.g. stream it across the first N seconds after the first stake) or require a minimum first stake.","line":88,"path":"src/FrogStaking.sol","reproduction":"Fixture (test/helpers/Fixture.sol) with seedPool(). 1) trade(true,true,10_000 ether) then trade(false,true,10_000 ether) with nobody staked: staking.queuedRewards() == 178.960045795707695373 IMD. 2) token.transfer(alice,1); alice approves and calls staking.stake(1). 3) staking.earned(alice) == 178.960045795707695373 IMD; alice.claim() returns exactly that amount in the same block. 4) bob then stakes 1,000,000 HFROG: staking.earned(bob) == 0. Expected under the brief's intent: the no-staker fees are shared by the early staker cohort pro rata to stake; actual: 100% to a 1-wei stake. Reproduced in test/scratch/Probe.t.sol::testOneWeiFirstStakerCapturesQueue (not kept).","severity":"low","snippet":"        _allocate(0);","title":"Queued no-staker rewards are captured in full by whoever stakes first, even 1 wei (economics x asymmetry seam)"},{"citation":"resolved","description":"The hook charges its 1.5% on three different bases. Exact-input buy (beforeSwap, line 124): fee = gross/10000*150, i.e. 1.5% of what the buyer pays. Exact-output sell (beforeSwap, line 124, divisor 9850): fee grossed up so the fee is 1.5% of what the pool pays out. Exact-input sell (afterSwap, line 150): 1.5% of gross pool output. Exact-output buy (afterSwap, line 150): fee = 1.5% of the pool's IMD input, which is then added on top, so the buyer's gross outlay is 1.015x the pool input and the fee is 150/10150 = 1.478% of gross. The branch for buys in afterSwap should gross up with divisor 10000-FEE_BPS (=9850) the way the exact-output sell branch already does, so that 'add 1.5% on buys' means the same number in both buy modes. Impact is a few basis points of fee per exact-output buy (about 1.1 bps of gross) and a router that can choose the cheaper mode; no funds are at risk. The README's fee table documents the four bases, so this may be an accepted choice; it is reported because the brief states one rate for buys and the two buy modes do not match it.","line":150,"path":"src/HashFrogHook.sol","reproduction":"Fixture with seedPool(), IMD and HFROG at 1:1. a) trade(true,true,100 ether): hook.totalFees() grows by 1.500000000000000000 IMD = 15000 ppm of the 100 IMD gross. b) trade(true,false,98 ether) (exact-output buy of 98 HFROG): buyer pays 100.732060676377574131 IMD gross, hook fee is 1.488651143000653804 IMD = 14778 ppm of gross; a 1.5%-of-gross fee would be 1.510980910145663612 IMD. Expected: same 1.5% of gross on both buy modes; actual: 1.500% vs 1.478%. Reproduced in test/scratch/Probe.t.sol::testBuyFeeModeAsymmetry (not kept). Fix: in afterSwap, when imdDelta < 0 (buy) use fee = amount * FEE_BPS / (10000 - FEE_BPS).","severity":"low","snippet":"            fee = amount * FEE_BPS / 10000;","title":"Exact-output buys pay 1.478% of gross IMD while the other three swap modes pay 1.5% of gross (fee-base asymmetry between branches)"},{"citation":"resolved","description":"HashFrogHook takes IERC20 imd_ as its third constructor argument and needs it at construction (it is baked into FrogStaking, FrogRouter and HashFrog, which the constructor deploys). The deployer resolves only \"$poolManager\" and \"$token\"; the launch schema carries the paired currency as pool.pairedCurrency, a literal lowercase address. The checked-in parameter record writes \"$imd\", which the deployer does not know, and docs/DEPLOYMENT.md pushes the substitution to the coordinator. If the manifest author copies this record, ABI encoding of the constructor arguments fails or the deployer refuses the launch. This is a configuration gap, not a code defect: the manifest must carry the verified IMD address as a literal in hook.constructorArgs[2] and the same address in pool.pairedCurrency, and the mainnet rehearsal (script/MainnetRehearsal.s.sol, still unrun) must be run with that address. No other owner, admin, upgrade or pause input exists to review; the constructor's only trusted inputs are the manager, token, IMD and tick spacing, and the two payout addresses are compile-time constants that match the brief.","line":15,"path":"launch.parameters.json","reproduction":"State: launch.parameters.json as checked in (status 'prepared-not-deployed'), no launch.json in the tree. Input: a manifest whose hook.constructorArgs is copied from line 15. Expected: deployer encodes (address poolManager, address token, address imd, int24 60). Actual: the third element is the string \"$imd\", which is neither an address nor a documented placeholder, so the hook cannot be constructed from the manifest as written. Verified also that with any nonzero literal IMD the hook constructs and passes the verifier's protected floor (test/scratch copy of Hook.protected.t.sol with IMD_HOOK_CREATION_CODE = HashFrogHook creation code ++ abi.encode(manager, tokenProbe, 0x3000...0003, 60): 3 of 3 pass).","severity":"info","snippet":"    \"constructorArgs\": [\"$poolManager\", \"$token\", \"$imd\", 60],","title":"Hook constructor needs the IMD address as a literal; the parameter record uses an undocumented \"$imd\" placeholder and no launch.json exists yet"}],"hash":"b0ae12b6095643c45a21781639ed139ad7dfc4466a4cb5f2d585bf7aec14e91e","nodeId":"3186382e-ce85-4036-b702-785d4f3f09ba","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds three findings and a 32-row coverage record, and no source file was changed.\n\n**Outcome.** The permission model is sound. Every state-changing entry point is guarded by an immutable (`poolManager`, `hook`) or scoped to the caller's own position, the three ClaimReceiver redemption destinations are fixed at construction, and I found no path that moves ETH, IMD or HFROG anywhere the brief does not name. Nothing in the assigned area rises above low.\n\n**Findings reported**\n- **Low, staking.** The 60% staker share accrued while nobody is staked goes entirely to the first staker, even a 1-wei stake. In a probe, trading 20,000 IMD with no stakers queued about 179 IMD, which a 1-wei staker claimed in the next transaction while a later 1,000,000 HFROG staker got nothing. The README calls this by design, so it is framed as a confirmation for the author.\n- **Low, hook.** Exact-output buys are charged 1.5% of the pool input instead of 1.5% of gross, so they pay 1.478% of gross while the other three swap modes pay 1.500%. A one-line divisor change in `afterSwap` makes both buy modes match the brief.\n- **Info, launch record.** The hook needs the IMD address as a literal constructor argument, but the checked-in parameter record writes an undocumented `$imd` placeholder and no manifest exists yet. The manifest author must substitute the verified address.\n\n**Verified in passing**\n- A mainnet fork at block 26140055 showed the live hackathon contract accepting the 85% share from the frog contract inside the 100k-gas call, with no credit left behind. The team address is an EOA.\n- A buy and a sell through the real mainnet PoolManager with this hook settled and the three recipients redeemed exactly their 60/25/15 split.\n- The verifier's protected hook floor passes on the actual creation code.\n\n**Not reached.** The browser miner, FrogArt rendering and the deeper oracle economics were only skimmed, since they fall outside the access, trust-gap and asymmetry area. The mainnet rehearsal script itself still cannot run until the coordinator supplies the IMD address.","treeHash":null,"usage":{"cachedInputTokens":2872000,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":73248,"runtime":"claude","turns":58,"wallClockMs":1499775}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ab846a7d68ed31f8","findings":[],"hash":"b94efac14b5c50a939f7c7dc8fa03b76bfc3faadda217582d3b40b4de473a976","nodeId":"776bd72a-f8e5-4937-ac8e-627586aeb9a9","outcome":"failed","summary":"the task produced no changes; the agent's last message was: Blocked by the workspace runtime: every command fails before execution with `bwrap: setting up uid map: Permission denied`. File writes also fail.\n\nI couldn’t read the supplied checks or modify files. Both `forge build` and `forge test` were attempted but could not start. Nothing was implemented, deployed, or hosted.\n\nPlease restore workspace execution and rerun this assignment.","treeHash":null,"usage":{"cachedInputTokens":89600,"inputTokens":24466,"model":"gpt-6-astra","outputTokens":942,"runtime":"codex","turns":3,"wallClockMs":35307}},{"artifacts":[],"attempt":1,"bundleHash":"c7f0d2d780d2ff4c061e3b3c5a5eea82762e2c900e8af9d32e49b2ce39df208a","device":"2bb6789ad7b554e8","findings":[],"hash":"ed0dac235254dab95a0ed1a13e109f6b9bed8bbea6cb28799b70493643e2156e","nodeId":"bde8ba06-6327-46f4-b590-ec9507e08367","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests, entirely under `test/`.\n\nValidation: `forge build` passed; `forge test` reported **68 passed, 0 failed, 1 skipped**. Mainnet verification remains pending RPC and verified launch inputs. No contract defects reproduced.\n\nCoverage and reproduction details: [test/REVIEW.md](/Users/heiry/.identitymd/work/703944b1-8946-4995-945a-3f61749f8545/bde8ba06-6327-46f4-b590-ec9507e08367/test/REVIEW.md).","treeHash":"09296d0d67b09505e5fd7cf81909e89fa83e3e31","usage":{"cachedInputTokens":3281664,"inputTokens":139241,"model":"gpt-6-astra","outputTokens":29371,"runtime":"codex","turns":9,"wallClockMs":1082131}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddcecd7483f623f7","findings":[{"citation":"resolved","description":"The brief asks for 1.5% on buys and 1.5% on sells taken in IMD. Three of the four order modes implement that as 1.5% of the gross IMD that crosses the swapper's wallet: exact-input buys take 150/10000 of the IMD budget (line 124, divisor 10000), exact-input sells take 150/10000 of the gross IMD the pool pays out, and exact-output sells gross up the net IMD with 150/9850 so the fee is exactly 1.5% of gross (line 124, divisor 9850). The exact-output buy branch in afterSwap (line 150) instead applies 150/10000 to `amount`, which here is the pool's IMD input before the hook fee is added on top. The swapper pays `amount + fee`, so the fee is 150/10150 = 1.4778% of what they actually pay, 0.022 percentage points below the 1.5% the other buy mode charges. The FrogRouter and the site only issue exact-input orders, so this path is reached by external routers (e.g. Universal Router exact-output buys). Impact is a slightly under-collected hook fee on that path and an inconsistent fee definition between the two buy modes. The README documents the formula, but it does not match the symmetrical treatment of exact-output sells. Minimal fix that preserves intent: for exact-output buys use `fee = amount * FEE_BPS / (10000 - FEE_BPS)` so the fee is 1.5% of `amount + fee`, mirroring the exact-output sell gross-up.","line":150,"path":"src/HashFrogHook.sol","reproduction":"Fixture setup (fresh PoolManager, HFROG/IMD pool at price 1, seedPool()). Call swaps.swap with exact output of 100 HFROG (amountSpecified = +100e18, buy direction). Observed on this tree (test/scratch/Probe.t.sol::testExactOutputBuyFeeBasis): swapper's IMD debit = 102.785837984962128103 IMD, hook fee = 1.519002531797469873 IMD, pool input = 101.266835453164658230 IMD, fee / total paid = 14778 (0.014778). Then exact-output sell of 100 IMD net in the same fixture: fee = 1.522842639593908629 IMD, fee / gross = 14999 (0.015). Expected: both exact-output modes charge 1.5% of the gross IMD that crosses the swapper's wallet; actual: the buy charges 1.4778%.","severity":"low","snippet":"            fee = amount * FEE_BPS / 10000;","title":"Exact-output buys charge 1.5% of pool input, not of gross IMD paid, unlike the other three swap modes"},{"citation":"resolved","description":"HashFrogHook's constructor is (IPoolManager, IERC20 token, IERC20 imd, int24 spacing). The launch reference defines only two deployer-resolved placeholders, \"$poolManager\" and \"$token\". There is no \"$imd\" placeholder, so the authoritative launch.json must carry the verified IMD address as a literal string in constructorArgs[2] (and the same address in pool.pairedCurrency). If the manifest is produced by copying this record, the deployer either refuses the unknown placeholder or passes it as a non-address string, and the hook cannot be deployed. launch.parameters.json is explicitly a preparation record and no launch.json exists yet, so this is a handoff note, not a code defect; docs/DEPLOYMENT.md line 9 already says to resolve $imd. Recorded here so the manifest author does not miss it.","line":15,"path":"launch.parameters.json","reproduction":"Validate a launch.json whose hook.constructorArgs is [\"$poolManager\", \"$token\", \"$imd\", \"60\"] against the LaunchManifest schema and deployer placeholder resolution: \"$imd\" has no resolution rule, so the deployment plan fails or encodes a non-address. Expected: constructorArgs[2] is the literal 0x-prefixed IMD address that beforeInitialize later validates for code and 18 decimals.","severity":"info","snippet":"    \"constructorArgs\": [\"$poolManager\", \"$token\", \"$imd\", 60],","title":"Preparation record uses an undefined \"$imd\" placeholder for the hook's third constructor argument"}],"hash":"f954c8d37aea737c297075b42c1821983edea9bb9af911832c97a12793ed3177","nodeId":"1cc013a0-4933-4a7a-b000-69b3eecea8f4","outcome":"completed","summary":"Review complete. The findings and coverage record are in `.imd-findings.json`; no project files were changed (only the gitignored `test/scratch/` probes and the findings file were written).\n\n**Outcome:** the control-flow area is clean of fund-loss defects. I traced every one of the 29 listed entry points through its guards, state changes and failure paths against the Execution Trace, Periphery and First Principles checklists, and verified the v4 delta plumbing at the pinned v4-core commit. All 47 existing tests pass, and eight additional scratch probes on the real PoolManager confirmed designed behavior.\n\n**Findings reported**\n\n- **Low, `src/HashFrogHook.sol:150`.** Exact-output buys charge 1.5% of the pool's IMD input rather than of the gross IMD the swapper pays, so the effective rate is 1.4778%. The other three order modes, including exact-output sells which gross up with a 9850 divisor, all charge exactly 1.5% of gross. Reproduced with concrete amounts on the fixture pool. Reachable only via external routers since FrogRouter and the site issue exact-input orders. Suggested fix mirrors the sell gross-up.\n- **Info, `launch.parameters.json:15`.** The preparation record uses a `$imd` placeholder that the deployer has no resolution rule for. The authoritative manifest must carry the literal IMD address. The deployment doc already says this.\n\n**Leads checked and rejected** (static-analysis lines and my own probes): flush reentrancy, oracle \"weak PRNG\" modulo, afterSwap casting, partial-fill fee leakage, buyback cooldown consumption on failure, 64-block reference boundary, second-pool initialization, staking remainder overpayment, claim backing on a fresh manager with token-only liquidity, stake-sandwich capture (works as designed, documented, 24h lock applies).\n\n**Coverage:** 29 entry-point rows plus 4 invariant/periphery rows, all `holds` except `afterSwap` which carries the low finding. Nothing was left `unreached`. Outside my area, the documented economic risks (30-minute endogenous TWAP floor, first-staker queue capture, live-supply burn denominator) were noted but not re-reported since the design explicitly accepts them and sandwiching the buyback is fee-unprofitable at any depth where the buyback itself can clear.","treeHash":null,"usage":{"cachedInputTokens":2368785,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":68030,"runtime":"claude","turns":49,"wallClockMs":1248930}}],"verification":[{"checks":[{"durationMs":66228,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 65.59s\nCompiler run successful!\nwarning[weak-prng]: weak randomness derived from a predictable on-chain value\n   ╭▸ src/HashFrog.sol:93:24\n   │\n93 │         bytes32 seed = keccak256(abi.encodePacked(msg.sender, nonce, expectedSeed, blockhash(refBlock)));\n   │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/weak-prng\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/FrogRouter.sol:36:68\n   │\n36 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, address hook_, int24 spacing_) {\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/FrogStaking.sol:42:68\n   │\n42 │     constructor(IPoolManager manager_, IERC20 token_, IERC20 imd_, address hook_)\n   │                                                                    ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:59:40\n   │\n59 │         (spent, received) = abi.decode(poolManager.unlock(abi.encode(buy, amountIn, priceLimit)), (uint256, uint256));\n   │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/FrogRouter.sol:55:13\n   │\n55 │         if (block.timestamp > deadline) revert Expired();\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/FrogRouter.sol:62:9\n   │\n62 │         emit Swapped(msg.sender, buy, spent, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:74:13\n   │\n74 │         try poolManager.unlock(abi.encode(buy, amountIn, priceLimit)) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:79:40\n   │\n79 │             if (reason.length != 68 || bytes4(reason) != QuoteResult.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/FrogRouter.sol:90:37\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/FrogRouter.sol:90:45\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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[arbitrary-send-erc20]: `transferFrom` uses an arbitrary `from`; require it to equal `msg.sender` or `address(this)`\n    ╭▸ src/FrogRouter.sol:110:9\n    │\n110 │         tokenIn.safeTransferFrom(payer, address(poolManager), spent);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-erc20\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:99:82\n   │\n99 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(zeroForOne, -int256(amount), limit), \"\");\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/FrogRouter.sol:103:25\n    │\n103 │         uint256 spent = uint256(-int256(input));\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[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/HashFrog.sol:64:88\n   │\n64 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, FrogRouter router_, address hook_)\n   │                                                                                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FrogRouter.sol:104:28\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:104:36\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:111:9\n    │\n111 │         poolManager.settle();\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n   ╭▸ src/FrogArt.sol:47:24\n   │\n47 │                 (270 + (i / 16) * 10).toString(),\n   │                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/ClaimReceiver.sol:23:53\n   │\n23 │     constructor(IPoolManager manager_, IERC20 imd_, address recipient_) {\n   │                                                     ━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:23:23\n   │\n23 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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/FrogOracle.sol:56:14\n   │\n56 │         if (!found) revert OracleNotReady();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:39:23\n   │\n39 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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/FrogOracle.sol:63:20\n   │\n63 │         meanTick = int24(change / 1800);\n   │                    ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int24' 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/HashFrog.sol:88:34\n   │\n88 │         if (totalMinted >= 60 && block.timestamp < oldest + 3600) revert HourFull(oldest + 3600);\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/HashFrog.sol:101:9\n    │\n101 │         emit Mined(id, msg.sender, seed, target);\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/HashFrog.sol:119:9\n    │\n119 │         emit Retargeted(old, target, elapsed);\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/HashFrog.sol:141:9\n    │\n141 │         emit EthPayment(recipient, amount, ok);\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/HashFrog.sol:112:27\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:112:49\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:114:24\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\n    │                        ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/FrogStaking.sol:87:13\n   │\n87 │         if (hfrog.balanceOf(address(this)) - beforeBalance != amount) revert UnsupportedToken();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HashFrog.sol:114:41\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\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/FrogStaking.sol:106:13\n    │\n106 │         if (block.timestamp < p.unlockAt) revert TooEarly(p.unlockAt);\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/HashFrog.sol:127:17\n    │\n127 │             if (block.timestamp >= expiry) count++;\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/ClaimReceiver.sol:49:9\n   │\n49 │         emit FeesRedeemed(redemptionRecipient, 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/FrogStaking.sol:122:9\n    │\n122 │         emit Claimed(msg.sender, amount);\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/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_owners` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/HashFrog.sol:175:9\n    │\n175 │         emit Burned(id, msg.sender, hfrogAmount, imdAmount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/HashFrog.sol:193:43\n    │\n193 │         (uint256 spent, uint256 bought) = router.swap(true, amount, minOut, priceLimit, deadline);\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HashFrog.sol:185:13\n    │\n185 │         if (block.timestamp < lastBuyback + 60) revert BuybackCooldown(lastBuyback + 60);\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/HashFrog.sol:198:9\n    │\n198 │         emit Buyback(spent, bought);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrog.sol:187:62\n    │\n187 │         uint256 floor = oracle.quoteAtTick(oracle.consult(), uint128(amount), address(imd) < address(hfrog)) * 95 / 100;\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[missing-events-arithmetic]: `tickCumulative` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:25:9\n   │\n25 │         tickCumulative = cumulative;\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastObservationTime` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:26:9\n   │\n26 │         lastObservationTime = now_;\n   │         ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:27:9\n   │\n27 │         lastTick = tick;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:32:13\n   │\n32 │             observationIndex = (observationIndex + 1) % 64;\n   │             ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/HashFrogHook.sol:98:9\n   │\n98 │         emit PoolBound(PoolId.unwrap(key.toId()));\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:109:18\n    │\n109 │         amount = uint256(n < 0 ? -n : n);\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/HashFrogHook.sol:110:37\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:110:45\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:128:78\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                              ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:125:32\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:125:40\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:128:63\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:128:70\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:155:59\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:141:26\n    │\n141 │         uint256 amount = uint256(imdDelta < 0 ? -int256(imdDelta) : int256(imdDelta));\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/HashFrogHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/HashFrogHook.sol:172:9\n    │\n172 │         emit FeeAccrued(fee, stakers, vault, team);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":530,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Oracle.t.sol:OracleTest\n[PASS] testRingWrapAndRapidUpdatesCannotEraseHistory() (gas: 8389821)\n[PASS] testWarmupHistoryInterpolationAndNegativeRounding() (gas: 221745)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.85ms (1.75ms CPU time)\n\nRan 1 test for test/Admission.t.sol:AdmissionTest\n[PASS] testConstructorProbeAndMissingPairInitialization() (gas: 11373520)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.61ms (7.32ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:HookTest\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 756043, ~: 757107)\nLogs:\n  Bound result 100000000\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 46.05ms (74.90ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency1Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 756011, ~: 757119)\nLogs:\n  Bound result 9664006630975637287225\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 48.89ms (62.85ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency0Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2029058)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1438465)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1207758)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 798680)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 762611, ~: 762715)\nLogs:\n  Bound result 100000000\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1570457)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235298)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 48.94ms (68.92ms CPU time)\n\nRan 6 tests for test/Staking.t.sol:StakingTest\n[PASS] testEmptyRewardsGoToFirstStakerAndExitStopsEarnings() (gas: 1894773)\n[PASS] testFuzzRoundingNeverOverpays(uint96,uint96,uint96) (runs: 128, μ: 1503135, ~: 1511501)\nLogs:\n  Bound result 9715215260856199898071039\n  Bound result 13348\n  Bound result 999999999999900017684\n\n[PASS] testLateStakeCannotCaptureAlreadyAccruedRewards() (gas: 1284341)\n[PASS] testPartialExitAndAdditionalExitRestartsDelay() (gas: 366663)\n[PASS] testRewardsAccrueAtSwapAndConserve() (gas: 1516186)\n[PASS] testUnauthorizedNotificationInvalidAmountsAndExcessExit() (gas: 117319)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 59.83ms (47.88ms CPU time)\n\nRan 13 tests for test/NFT.t.sol:NFTTest\n[PASS] testBurnExactShareDustAndHolderOnly() (gas: 1702072)\n[PASS] testERC721CallbackCannotReenterPayments() (gas: 580657)\n[PASS] testEverMintedCapAndNeverReuseBurnedId() (gas: 396822950)\n[PASS] testExactEthSplitCreditAndFlush() (gas: 1044795)\n[PASS] testForcedEthOnlyGoesToFixedRecipients() (gas: 190257)\n[PASS] testFuzzBurnConservation(uint96,uint96,uint8) (runs: 128, μ: 2289743, ~: 1916232)\nLogs:\n  Bound result 3\n  Bound result 18807\n  Bound result 82\n\n[PASS] testGasExhaustionCreatesRetryableCredit() (gas: 632709)\n[PASS] testNoWithdrawalOrAdminSelectors() (gas: 287314)\n[PASS] testOnePerTransactionAcrossCallersAndReferenceBoundary() (gas: 806971)\n[PASS] testProductionDifficultyAndGenesis() (gas: 5369371)\n[PASS] testRetargetHarderEasierAndSaturation() (gas: 6085527)\n[PASS] testRollingWindowNotCalendarHour() (gas: 15211615)\n[PASS] testWrongPriceReferenceSeedProofAndSenderBinding() (gas: 691210)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 289.80ms (346.13ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantFeeConservationAndNoRecipientSubstitution\n[PASS] invariantPrincipalAndPendingRewardsStaySolvent\n AccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 379)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| PondHandler | advance     | 235   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | buyback     | 270   | 142     | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | claim       | 260   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | redeem      | 258   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | requestExit | 267   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | stake       | 232   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | swap        | 285   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | unstake     | 241   | 237     | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 255\n  Bound result 9999999999900000132\n  Bound result 4222\n  Bound result 73\n  Bound result 2607\n  Bound result 6891475712795310644\n  Bound result 437\n  Bound result 1216251833929039549346\n  Bound result 9\n  Bound result 2221339670\n  Bound result 2221339669\n  Bound result 1836\n  Bound result 414269577\n  Bound result 127\n  Bound result 9608\n  Bound result 9999999999900001077\n  Bound result 1232766\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 404.37ms (394.56ms CPU time)\n\nRan 8 test suites in 405.69ms (916.34ms CPU time): 47 tests passed, 0 failed, 0 skipped (47 total tests)\n","passed":true},{"durationMs":63,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrogRouter.quote(bool,uint256,uint160)\",\"FrogRouter.swap(bool,uint256,uint256,uint160,uint256)\",\"FrogRouter.unlockCallback(bytes)\",\"FrogStaking.claim()\",\"FrogStaking.notifyReward(uint256)\",\"FrogStaking.redeemFees()\",\"FrogStaking.requestUnstake(uint256)\",\"FrogStaking.stake(uint256)\",\"FrogStaking.unlockCallback(bytes)\",\"FrogStaking.unstake()\",\"HFROG.approve(address,uint256)\",\"HFROG.transfer(address,uint256)\",\"HFROG.transferFrom(address,address,uint256)\",\"HashFrog.approve(address,uint256)\",\"HashFrog.burn(uint256)\",\"HashFrog.buyback(uint256,uint256,uint160,uint256)\",\"HashFrog.flush()\",\"HashFrog.mine(uint256,bytes32,uint256)\",\"HashFrog.redeemFees()\",\"HashFrog.safeTransferFrom(address,address,uint256)\",\"HashFrog.safeTransferFrom(address,address,uint256,bytes)\",\"HashFrog.setApprovalForAll(address,bool)\",\"HashFrog.transferFrom(address,address,uint256)\",\"HashFrog.unlockCallback(bytes)\",\"HashFrogHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"HashFrogHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"HashFrogHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"HashFrogHook.redeemFees()\",\"HashFrogHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"README.md\":124,\"dependencies.json\":43,\"docs/DEPLOYMENT.md\":53,\"docs/SECURITY.md\":48,\"docs/validation.json\":59,\"docs/vendor-sha256.json\":512,\"foundry.toml\":24,\"launch.parameters.json\":34,\"package.json\":1,\"script/MainnetRehearsal.s.sol\":90,\"script/PrepareLaunch.s.sol\":31,\"site/abi.json\":1,\"site/app.js\":206,\"site/config.json\":6,\"site/index.html\":27,\"site/miner/common.js\":29,\"site/miner/keccak.wasm\":12,\"site/miner/keccak.wat\":201,\"site/miner/keccak.wgsl\":116,\"site/miner/worker.js\":98,\"site/pond.svg\":1,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/ClaimReceiver.sol\":60,\"src/FrogArt.sol\":100,\"src/FrogOracle.sol\":76,\"src/FrogRouter.sol\":115,\"src/FrogStaking.sol\":124,\"src/HFROG.sol\":11,\"src/HashFrog.sol\":205,\"src/HashFrogHook.sol\":174,\"src/HookFlags.sol\":28,\"test/Admission.t.sol\":36,\"test/Hook.t.sol\":220,\"test/Invariant.t.sol\":126,\"test/NFT.t.sol\":406,\"test/Oracle.t.sol\":50,\"test/Staking.t.sol\":137,\"test/helpers/Create2Deployer.sol\":10,\"test/helpers/Fixture.sol\":107,\"test/mocks/MockERC20.sol\":13,\"test/site/integration.mjs\":69,\"test/site/miner.test.mjs\":32,\"test/site/static.test.mjs\":20,\"tools/build-miner.mjs\":7,\"tools/configure-site.mjs\":30,\"tools/export-abi.py\":5,\"tools/generate-miner.py\":92,\"tools/vendor/linkedom.LICENSE\":15,\"tools/vendor/linkedom.js\":12761,\"tools/vendor/wabt.LICENSE\":202,\"tools/vendor/wabt.cjs\":38},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":10049,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/FrogOracle.sol:38: FrogOracle.consult() (src/FrogOracle.sol#38-65) uses a weak PRNG: \"change < 0 && change % 1800 != 0 (src/FrogOracle.sol#64)\"\n[high/medium] reentrancy-eth at src/HashFrog.sol:145: Reentrancy in HashFrog.flush() (src/HashFrog.sol#145-155):\n[medium/medium] divide-before-multiply at src/FrogArt.sol:19: FrogArt.svg(bytes32) (src/FrogArt.sol#19-73) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/HashFrog.sol:135: HashFrog._pay(address,uint256) (src/HashFrog.sol#135-142) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/FrogOracle.sol:45: FrogOracle.consult().found (src/FrogOracle.sol#45) is a local variable never initialized\n[medium/medium] uninitialized-local at src/HashFrogHook.sol:142: HashFrogHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).fee (src/HashFrogHook.sol#142) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FrogOracle.sol:44: FrogOracle.consult().lower (src/FrogOracle.sol#44) is a local variable never initialized\n[medium/medium] uninitialized-local at src/HashFrogHook.sol:121: HashFrogHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/HashFrogHook.sol#121) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FrogArt.sol:22: FrogArt.svg(bytes32).tiles (src/FrogArt.sol#22) is a local variable never initialized\n[medium/medium] unused-return at src/FrogRouter.sol:66: FrogRouter.quote(bool,uint256,uint160) (src/FrogRouter.sol#66-87) ignores return value by poolManager.unlock(abi.encode(buy,amountIn,priceLimit)) (src/FrogRouter.sol#74-86)\n[medium/medium] unused-return at src/FrogRouter.sol:93: FrogRouter.unlockCallback(bytes) (src/FrogRouter.sol#93-114) ignores return value by poolManager.settle() (src/FrogRouter.sol#111)\n[medium/medium] unused-return at src/ClaimReceiver.sol:42: ClaimReceiver._redeemFees() (src/ClaimReceiver.sol#42-50) ignores return value by poolManager.unlock(abi.encode(amount)) (src/ClaimReceiver.sol#46)\n[medium/medium] unused-return at src/HashFrogHook.sol:131: HashFrogHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/HashFrogHook.sol#131-156) ignores return value by (None,tick,None,None) = poolManager.getSlot0(key.toId()) (src/HashFrogHook.sol#153)\n[low/medium] missing-zero-check at src/FrogRouter.sol:36: FrogRouter.constructor(IPoolManager,IERC20,IERC20,address,int24).hook_ (src/FrogRouter.sol#36) lacks a zero-check on :\n[low/medium] missing-zero-check at src/HashFrog.sol:64: HashFrog.constructor(IPoolManager,IERC20,IERC20,FrogRouter,address).hook_ (src/HashFrog.sol#64) lacks a zero-check on :\n[low/medium] missing-zero-check at src/FrogStaking.sol:42: FrogStaking.constructor(IPoolManager,IERC20,IERC20,address).hook_ (src/FrogStaking.sol#42) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/FrogRouter.sol:66: Reentrancy in FrogRouter.quote(bool,uint256,uint160) (src/FrogRouter.sol#66-87):\n[low/medium] reentrancy-benign at src/HashFrogHook.sol:131: Reentrancy in HashFrogHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/HashFrogHook.sol#131-156):\n[low/medium] reentrancy-benign at src/HashFrog.sol:179: Reentrancy in HashFrog.buyback(uint256,uint256,uint160,uint256) (src/HashFrog.sol#179-199):\n[low/medium] reentrancy-benign at src/ClaimReceiver.sol:42: Reentrancy in ClaimReceiver._redeemFees() (src/ClaimReceiver.sol#42-50):\n[low/medium] reentrancy-benign at src/HashFrog.sol:135: Reentrancy in HashFrog._pay(address,uint256) (src/HashFrog.sol#135-142):\n[low/medium] reentrancy-benign at src/FrogRouter.sol:50: Reentrancy in FrogRouter.swap(bool,uint256,uint256,uint160,uint256) (src/FrogRouter.sol#50-63):\n[low/medium] reentrancy-events at src/HashFrogHook.sol:158: Reentrancy in HashFrogHook._accrue(uint256) (src/HashFrogHook.sol#158-173):\n[low/medium] timestamp at src/HashFrog.sol:109: HashFrog._retarget() (src/HashFrog.sol#109-120) uses timestamp for comparisons\n[low/medium] timestamp at src/HashFrog.sol:179: HashFrog.buyback(uint256,uint256,uint160,uint256) (src/HashFrog.sol#179-199) uses timestamp for comparisons\n[low/medium] timestamp at src/FrogStaking.sol:103: FrogStaking.unstake() (src/FrogStaking.sol#103-112) uses timestamp for comparisons\n[low/medium] timestamp at src/HashFrog.sol:122: HashFrog.freeSlots() (src/HashFrog.sol#122-131) uses timestamp for comparisons\n[low/medium] timestamp at src/FrogRouter.sol:50: FrogRouter.swap(bool,uint256,uint256,uint160,uint256) (src/FrogRouter.sol#50-63) uses timestamp for comparisons\n[low/medium] timestamp at src/FrogOracle.sol:22: FrogOracle._record(int24) (src/FrogOracle.sol#22-36) uses timestamp for comparisons\n[low/medium] timestamp at src/HashFrog.sol:75: HashFrog.mine(uint256,bytes32,uint256) (src/HashFrog.sol#75-107) uses timestamp for comparisons\n[low/medium] timestamp at src/FrogOracle.sol:38: FrogOracle.consult() (src/FrogOracle.sol#38-65) uses timestamp for comparisons","passed":true},{"durationMs":773,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/FrogRouter.sol:59: Reentrancy: State change after external call (6 places)\n[high] unsafe-casting at src/HashFrogHook.sol:155: Unsafe Casting of integers\n[low] internal-function-used-once at src/FrogArt.sol:12: Internal Function Used Only Once (3 places)\n[low] large-numeric-literal at src/HFROG.sol:9: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/FrogArt.sol:14: Literal Instead of Constant (76 places)\n[low] state-change-without-event at src/ClaimReceiver.sol:52: State Change Without Event (2 places)\n[low] unchecked-return at src/ClaimReceiver.sol:46: Unchecked Return (6 places)\n[low] uninitialized-local-variable at src/FrogArt.sol:41: Uninitialized Local Variable (3 places)\n[low] unused-public-function at src/FrogStaking.sol:74: Public Function Not Used Internally (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0fa7f73a28b44b4dfd83f974ad5cb7bfed1987681d3bf11c9b2ebf37161dd51b","verifiedTreeHash":"bec15f5ea5c12d5bec35f610f707678d53a43703","verifierVersion":"0.1.0+be003835"},{"checks":[{"durationMs":62218,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 61.90s\nCompiler run successful!\nwarning[weak-prng]: weak randomness derived from a predictable on-chain value\n   ╭▸ src/HashFrog.sol:93:24\n   │\n93 │         bytes32 seed = keccak256(abi.encodePacked(msg.sender, nonce, expectedSeed, blockhash(refBlock)));\n   │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/weak-prng\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/ClaimReceiver.sol:23:53\n   │\n23 │     constructor(IPoolManager manager_, IERC20 imd_, address recipient_) {\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/FrogStaking.sol:42:68\n   │\n42 │     constructor(IPoolManager manager_, IERC20 token_, IERC20 imd_, address hook_)\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/FrogRouter.sol:36:68\n   │\n36 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, address hook_, int24 spacing_) {\n   │                                                                    ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:59:40\n   │\n59 │         (spent, received) = abi.decode(poolManager.unlock(abi.encode(buy, amountIn, priceLimit)), (uint256, uint256));\n   │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/FrogRouter.sol:55:13\n   │\n55 │         if (block.timestamp > deadline) revert Expired();\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/FrogRouter.sol:62:9\n   │\n62 │         emit Swapped(msg.sender, buy, spent, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:74:13\n   │\n74 │         try poolManager.unlock(abi.encode(buy, amountIn, priceLimit)) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/HashFrog.sol:64:88\n   │\n64 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, FrogRouter router_, address hook_)\n   │                                                                                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:79:40\n   │\n79 │             if (reason.length != 68 || bytes4(reason) != QuoteResult.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/FrogRouter.sol:90:37\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/FrogRouter.sol:90:45\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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[arbitrary-send-erc20]: `transferFrom` uses an arbitrary `from`; require it to equal `msg.sender` or `address(this)`\n    ╭▸ src/FrogRouter.sol:110:9\n    │\n110 │         tokenIn.safeTransferFrom(payer, address(poolManager), spent);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-erc20\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:99:82\n   │\n99 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(zeroForOne, -int256(amount), limit), \"\");\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/FrogRouter.sol:103:25\n    │\n103 │         uint256 spent = uint256(-int256(input));\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/FrogRouter.sol:104:28\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:104:36\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:111:9\n    │\n111 │         poolManager.settle();\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/HashFrog.sol:88:34\n   │\n88 │         if (totalMinted >= 60 && block.timestamp < oldest + 3600) revert HourFull(oldest + 3600);\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/ClaimReceiver.sol:49:9\n   │\n49 │         emit FeesRedeemed(redemptionRecipient, 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/HashFrog.sol:101:9\n    │\n101 │         emit Mined(id, msg.sender, seed, target);\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/HashFrog.sol:119:9\n    │\n119 │         emit Retargeted(old, target, elapsed);\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/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/HashFrog.sol:141:9\n    │\n141 │         emit EthPayment(recipient, amount, ok);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/FrogStaking.sol:87:13\n   │\n87 │         if (hfrog.balanceOf(address(this)) - beforeBalance != amount) revert UnsupportedToken();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HashFrog.sol:112:27\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:112:49\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:114:24\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\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/HashFrog.sol:114:41\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\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/FrogStaking.sol:106:13\n    │\n106 │         if (block.timestamp < p.unlockAt) revert TooEarly(p.unlockAt);\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/HashFrog.sol:127:17\n    │\n127 │             if (block.timestamp >= expiry) count++;\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:23:23\n   │\n23 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/FrogStaking.sol:122:9\n    │\n122 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n   ╭▸ src/FrogOracle.sol:56:14\n   │\n56 │         if (!found) revert OracleNotReady();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:39:23\n   │\n39 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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/FrogOracle.sol:63:20\n   │\n63 │         meanTick = int24(change / 1800);\n   │                    ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int24' 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/FrogArt.sol:47:24\n   │\n47 │                 (270 + (i / 16) * 10).toString(),\n   │                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_owners` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/HashFrog.sol:175:9\n    │\n175 │         emit Burned(id, msg.sender, hfrogAmount, imdAmount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/HashFrog.sol:193:43\n    │\n193 │         (uint256 spent, uint256 bought) = router.swap(true, amount, minOut, priceLimit, deadline);\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HashFrog.sol:185:13\n    │\n185 │         if (block.timestamp < lastBuyback + 60) revert BuybackCooldown(lastBuyback + 60);\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/HashFrog.sol:198:9\n    │\n198 │         emit Buyback(spent, bought);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrog.sol:187:62\n    │\n187 │         uint256 floor = oracle.quoteAtTick(oracle.consult(), uint128(amount), address(imd) < address(hfrog)) * 95 / 100;\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[missing-events-arithmetic]: `tickCumulative` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:25:9\n   │\n25 │         tickCumulative = cumulative;\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastObservationTime` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:26:9\n   │\n26 │         lastObservationTime = now_;\n   │         ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:27:9\n   │\n27 │         lastTick = tick;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:32:13\n   │\n32 │             observationIndex = (observationIndex + 1) % 64;\n   │             ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/HashFrogHook.sol:98:9\n   │\n98 │         emit PoolBound(PoolId.unwrap(key.toId()));\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:109:18\n    │\n109 │         amount = uint256(n < 0 ? -n : n);\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/HashFrogHook.sol:110:37\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:110:45\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:128:78\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                              ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:125:32\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:125:40\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:128:63\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:128:70\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:155:59\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:141:26\n    │\n141 │         uint256 amount = uint256(imdDelta < 0 ? -int256(imdDelta) : int256(imdDelta));\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/HashFrogHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/HashFrogHook.sol:172:9\n    │\n172 │         emit FeeAccrued(fee, stakers, vault, team);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":475,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Oracle.t.sol:OracleTest\n[PASS] testRingWrapAndRapidUpdatesCannotEraseHistory() (gas: 8389821)\n[PASS] testWarmupHistoryInterpolationAndNegativeRounding() (gas: 221745)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.00ms (2.56ms CPU time)\n\nRan 1 test for test/Admission.t.sol:AdmissionTest\n[PASS] testConstructorProbeAndMissingPairInitialization() (gas: 11373520)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.88ms (8.73ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency0Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2029058)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1438465)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1207758)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 798680)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 762594, ~: 762715)\nLogs:\n  Bound result 36028797018963967\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1570457)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235298)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 55.59ms (65.06ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:HookTest\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 755917, ~: 757089)\nLogs:\n  Bound result 9999999999999900011086\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 55.82ms (80.45ms CPU time)\n\nRan 6 tests for test/Staking.t.sol:StakingTest\n[PASS] testEmptyRewardsGoToFirstStakerAndExitStopsEarnings() (gas: 1894773)\n[PASS] testFuzzRoundingNeverOverpays(uint96,uint96,uint96) (runs: 128, μ: 1504552, ~: 1513264)\nLogs:\n  Bound result 7690020327733440\n  Bound result 553700172\n  Bound result 100000003\n\n[PASS] testLateStakeCannotCaptureAlreadyAccruedRewards() (gas: 1284341)\n[PASS] testPartialExitAndAdditionalExitRestartsDelay() (gas: 366663)\n[PASS] testRewardsAccrueAtSwapAndConserve() (gas: 1516186)\n[PASS] testUnauthorizedNotificationInvalidAmountsAndExcessExit() (gas: 117319)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 59.64ms (55.70ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency1Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 756011, ~: 757113)\nLogs:\n  Bound result 32093515056822256122\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 64.56ms (100.25ms CPU time)\n\nRan 13 tests for test/NFT.t.sol:NFTTest\n[PASS] testBurnExactShareDustAndHolderOnly() (gas: 1702072)\n[PASS] testERC721CallbackCannotReenterPayments() (gas: 580657)\n[PASS] testEverMintedCapAndNeverReuseBurnedId() (gas: 396822950)\n[PASS] testExactEthSplitCreditAndFlush() (gas: 1044795)\n[PASS] testForcedEthOnlyGoesToFixedRecipients() (gas: 190257)\n[PASS] testFuzzBurnConservation(uint96,uint96,uint8) (runs: 128, μ: 2546013, ~: 2370041)\nLogs:\n  Bound result 1\n  Bound result 7690020327733440\n  Bound result 553700172\n\n[PASS] testGasExhaustionCreatesRetryableCredit() (gas: 632709)\n[PASS] testNoWithdrawalOrAdminSelectors() (gas: 287314)\n[PASS] testOnePerTransactionAcrossCallersAndReferenceBoundary() (gas: 806971)\n[PASS] testProductionDifficultyAndGenesis() (gas: 5369371)\n[PASS] testRetargetHarderEasierAndSaturation() (gas: 6085527)\n[PASS] testRollingWindowNotCalendarHour() (gas: 15211615)\n[PASS] testWrongPriceReferenceSeedProofAndSenderBinding() (gas: 691210)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 263.60ms (319.44ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantFeeConservationAndNoRecipientSubstitution\n[PASS] invariantPrincipalAndPendingRewardsStaySolvent\n AccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 396)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| PondHandler | advance     | 239   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | buyback     | 255   | 111     | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | claim       | 261   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | redeem      | 218   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | requestExit | 266   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | stake       | 267   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | swap        | 255   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | unstake     | 287   | 285     | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 9999999999900002644\n  Bound result 4691\n  Bound result 1889567282\n  Bound result 242\n  Bound result 6989\n  Bound result 4353\n  Bound result 8192\n  Bound result 9999999999900013039\n  Bound result 19408669798600602\n  Bound result 9999999999902088494\n  Bound result 5056619210721134084\n  Bound result 436655104\n  Bound result 11013\n  Bound result 13138\n  Bound result 869635047\n  Bound result 9999999999900005710\n  Bound result 6720\n  Bound result 8964\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 379.70ms (365.65ms CPU time)\n\nRan 8 test suites in 381.02ms (897.79ms CPU time): 47 tests passed, 0 failed, 0 skipped (47 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrogRouter.quote(bool,uint256,uint160)\",\"FrogRouter.swap(bool,uint256,uint256,uint160,uint256)\",\"FrogRouter.unlockCallback(bytes)\",\"FrogStaking.claim()\",\"FrogStaking.notifyReward(uint256)\",\"FrogStaking.redeemFees()\",\"FrogStaking.requestUnstake(uint256)\",\"FrogStaking.stake(uint256)\",\"FrogStaking.unlockCallback(bytes)\",\"FrogStaking.unstake()\",\"HFROG.approve(address,uint256)\",\"HFROG.transfer(address,uint256)\",\"HFROG.transferFrom(address,address,uint256)\",\"HashFrog.approve(address,uint256)\",\"HashFrog.burn(uint256)\",\"HashFrog.buyback(uint256,uint256,uint160,uint256)\",\"HashFrog.flush()\",\"HashFrog.mine(uint256,bytes32,uint256)\",\"HashFrog.redeemFees()\",\"HashFrog.safeTransferFrom(address,address,uint256)\",\"HashFrog.safeTransferFrom(address,address,uint256,bytes)\",\"HashFrog.setApprovalForAll(address,bool)\",\"HashFrog.transferFrom(address,address,uint256)\",\"HashFrog.unlockCallback(bytes)\",\"HashFrogHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"HashFrogHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"HashFrogHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"HashFrogHook.redeemFees()\",\"HashFrogHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"README.md\":124,\"dependencies.json\":43,\"docs/DEPLOYMENT.md\":53,\"docs/SECURITY.md\":48,\"docs/validation.json\":59,\"docs/vendor-sha256.json\":512,\"foundry.toml\":24,\"launch.json\":32,\"launch.parameters.json\":34,\"package.json\":1,\"script/MainnetRehearsal.s.sol\":90,\"script/PrepareLaunch.s.sol\":31,\"site/abi.json\":1,\"site/app.js\":206,\"site/config.json\":6,\"site/index.html\":27,\"site/miner/common.js\":29,\"site/miner/keccak.wasm\":12,\"site/miner/keccak.wat\":201,\"site/miner/keccak.wgsl\":116,\"site/miner/worker.js\":98,\"site/pond.svg\":1,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/ClaimReceiver.sol\":60,\"src/FrogArt.sol\":100,\"src/FrogOracle.sol\":76,\"src/FrogRouter.sol\":115,\"src/FrogStaking.sol\":124,\"src/HFROG.sol\":11,\"src/HashFrog.sol\":205,\"src/HashFrogHook.sol\":174,\"src/HookFlags.sol\":28,\"test/Admission.t.sol\":36,\"test/Hook.t.sol\":220,\"test/Invariant.t.sol\":126,\"test/NFT.t.sol\":406,\"test/Oracle.t.sol\":50,\"test/Staking.t.sol\":137,\"test/helpers/Create2Deployer.sol\":10,\"test/helpers/Fixture.sol\":107,\"test/mocks/MockERC20.sol\":13,\"test/site/integration.mjs\":69,\"test/site/miner.test.mjs\":32,\"test/site/static.test.mjs\":20,\"tools/build-miner.mjs\":7,\"tools/configure-site.mjs\":30,\"tools/export-abi.py\":5,\"tools/generate-miner.py\":92,\"tools/vendor/linkedom.LICENSE\":15,\"tools/vendor/linkedom.js\":12761,\"tools/vendor/wabt.LICENSE\":202,\"tools/vendor/wabt.cjs\":38},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8aa676f4b6276183090b9074f49e61856491661e03e61fc44ddc85a87342ff0d","verifiedTreeHash":"1c5b029854bb95d161f48845c9dfb3c97c82cdef","verifierVersion":"0.1.0+be003835"},{"checks":[{"durationMs":120410,"exitCode":0,"name":"build","output":"Compiling 120 files with Solc 0.8.26\nSolc 0.8.26 finished in 119.92s\nCompiler run successful!\nwarning[divide-before-multiply]: division before multiplication may lose precision\n   ╭▸ src/FrogArt.sol:47:24\n   │\n47 │                 (270 + (i / 16) * 10).toString(),\n   │                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[weak-prng]: weak randomness derived from a predictable on-chain value\n   ╭▸ src/HashFrog.sol:93:24\n   │\n93 │         bytes32 seed = keccak256(abi.encodePacked(msg.sender, nonce, expectedSeed, blockhash(refBlock)));\n   │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/weak-prng\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/FrogRouter.sol:36:68\n   │\n36 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, address hook_, int24 spacing_) {\n   │                                                                    ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:59:40\n   │\n59 │         (spent, received) = abi.decode(poolManager.unlock(abi.encode(buy, amountIn, priceLimit)), (uint256, uint256));\n   │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/FrogRouter.sol:55:13\n   │\n55 │         if (block.timestamp > deadline) revert Expired();\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/FrogRouter.sol:62:9\n   │\n62 │         emit Swapped(msg.sender, buy, spent, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/FrogRouter.sol:74:13\n   │\n74 │         try poolManager.unlock(abi.encode(buy, amountIn, priceLimit)) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:79:40\n   │\n79 │             if (reason.length != 68 || bytes4(reason) != QuoteResult.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/FrogRouter.sol:90:37\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/FrogRouter.sol:90:45\n   │\n90 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidAmount();\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[arbitrary-send-erc20]: `transferFrom` uses an arbitrary `from`; require it to equal `msg.sender` or `address(this)`\n    ╭▸ src/FrogRouter.sol:110:9\n    │\n110 │         tokenIn.safeTransferFrom(payer, address(poolManager), spent);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-erc20\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/HashFrog.sol:64:88\n   │\n64 │     constructor(IPoolManager manager_, IERC20 hfrog_, IERC20 imd_, FrogRouter router_, address hook_)\n   │                                                                                        ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogRouter.sol:99:82\n   │\n99 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(zeroForOne, -int256(amount), limit), \"\");\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/FrogRouter.sol:103:25\n    │\n103 │         uint256 spent = uint256(-int256(input));\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/FrogRouter.sol:104:28\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:104:36\n    │\n104 │         uint256 received = uint256(uint128(output));\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/FrogRouter.sol:111:9\n    │\n111 │         poolManager.settle();\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/FrogStaking.sol:42:68\n   │\n42 │     constructor(IPoolManager manager_, IERC20 token_, IERC20 imd_, address hook_)\n   │                                                                    ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/HashFrog.sol:88:34\n   │\n88 │         if (totalMinted >= 60 && block.timestamp < oldest + 3600) revert HourFull(oldest + 3600);\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/HashFrog.sol:101:9\n    │\n101 │         emit Mined(id, msg.sender, seed, target);\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/HashFrog.sol:119:9\n    │\n119 │         emit Retargeted(old, target, elapsed);\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/HashFrog.sol:141:9\n    │\n141 │         emit EthPayment(recipient, amount, ok);\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/HashFrog.sol:112:27\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:112:49\n    │\n112 │         uint256 bounded = elapsed < 120 ? 120 : elapsed > 960 ? 960 : elapsed;\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/HashFrog.sol:114:24\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\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/HashFrog.sol:114:41\n    │\n114 │         uint256 next = bounded > 480 && old > Math.mulDiv(type(uint256).max, 480, bounded)\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/HashFrog.sol:127:17\n    │\n127 │             if (block.timestamp >= expiry) count++;\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:23:23\n   │\n23 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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/FrogOracle.sol:56:14\n   │\n56 │         if (!found) revert OracleNotReady();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/FrogOracle.sol:39:23\n   │\n39 │         uint64 now_ = uint64(block.timestamp);\n   │                       ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' 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/FrogOracle.sol:63:20\n   │\n63 │         meanTick = int24(change / 1800);\n   │                    ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int24' 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/ClaimReceiver.sol:23:53\n   │\n23 │     constructor(IPoolManager manager_, IERC20 imd_, address recipient_) {\n   │                                                     ━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/FrogStaking.sol:87:13\n   │\n87 │         if (hfrog.balanceOf(address(this)) - beforeBalance != amount) revert UnsupportedToken();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FrogStaking.sol:106:13\n    │\n106 │         if (block.timestamp < p.unlockAt) revert TooEarly(p.unlockAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/FrogStaking.sol:122:9\n    │\n122 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/HashFrog.sol:137:22\n    │\n137 │         (bool ok,) = recipient.call{value: amount, gas: 100_000}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/ClaimReceiver.sol:49:9\n   │\n49 │         emit FeesRedeemed(redemptionRecipient, amount);\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/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_owners` is updated\n   ╭▸ src/ClaimReceiver.sol:46:9\n   │\n46 │         poolManager.unlock(abi.encode(amount));\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/HashFrog.sol:175:9\n    │\n175 │         emit Burned(id, msg.sender, hfrogAmount, imdAmount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/HashFrog.sol:193:43\n    │\n193 │         (uint256 spent, uint256 bought) = router.swap(true, amount, minOut, priceLimit, deadline);\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HashFrog.sol:185:13\n    │\n185 │         if (block.timestamp < lastBuyback + 60) revert BuybackCooldown(lastBuyback + 60);\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/HashFrog.sol:198:9\n    │\n198 │         emit Buyback(spent, bought);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrog.sol:187:62\n    │\n187 │         uint256 floor = oracle.quoteAtTick(oracle.consult(), uint128(amount), address(imd) < address(hfrog)) * 95 / 100;\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[missing-events-arithmetic]: `tickCumulative` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:25:9\n   │\n25 │         tickCumulative = cumulative;\n   │         ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastObservationTime` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:26:9\n   │\n26 │         lastObservationTime = now_;\n   │         ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:27:9\n   │\n27 │         lastTick = tick;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n   ╭▸ src/FrogOracle.sol:32:13\n   │\n32 │             observationIndex = (observationIndex + 1) % 64;\n   │             ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/HashFrogHook.sol:98:9\n   │\n98 │         emit PoolBound(PoolId.unwrap(key.toId()));\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:109:18\n    │\n109 │         amount = uint256(n < 0 ? -n : n);\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/HashFrogHook.sol:110:37\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:110:45\n    │\n110 │         if (amount == 0 || amount > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:128:78\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                              ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:125:32\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:125:40\n    │\n125 │             if (amount + fee > uint256(uint128(type(int128).max))) revert InvalidSwapAmount();\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/HashFrogHook.sol:128:63\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:128:70\n    │\n128 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/HashFrogHook.sol:155:59\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/HashFrogHook.sol:141:26\n    │\n141 │         uint256 amount = uint256(imdDelta < 0 ? -int256(imdDelta) : int256(imdDelta));\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/HashFrogHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/HashFrogHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/HashFrogHook.sol:172:9\n    │\n172 │         emit FeeAccrued(fee, stakers, vault, team);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":10568,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP] test_MainnetBuySellClaimBurnAndHackathonAcceptance() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 479.46µs (88.49µs CPU time)\n\nRan 2 tests for test/Oracle.t.sol:OracleTest\n[PASS] testRingWrapAndRapidUpdatesCannotEraseHistory() (gas: 8389821)\n[PASS] testWarmupHistoryInterpolationAndNegativeRounding() (gas: 221745)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.25ms (2.10ms CPU time)\n\nRan 1 test for test/Admission.t.sol:AdmissionTest\n[PASS] testConstructorProbeAndMissingPairInitialization() (gas: 11373520)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.61ms (5.58ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:HookTest\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 756039, ~: 757107)\nLogs:\n  Bound result 2122829437\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 90.70ms (113.35ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency0Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2029058)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1438465)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1207758)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 798680)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 762597, ~: 762715)\nLogs:\n  Bound result 9999999999999900001003\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1570457)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235298)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 108.79ms (109.75ms CPU time)\n\nRan 6 tests for test/Staking.t.sol:StakingTest\n[PASS] testEmptyRewardsGoToFirstStakerAndExitStopsEarnings() (gas: 1894773)\n[PASS] testFuzzRoundingNeverOverpays(uint96,uint96,uint96) (runs: 128, μ: 1505539, ~: 1513318)\nLogs:\n  Bound result 421714749251843\n  Bound result 3164495049563200410742028\n  Bound result 7560488092\n\n[PASS] testLateStakeCannotCaptureAlreadyAccruedRewards() (gas: 1284341)\n[PASS] testPartialExitAndAdditionalExitRestartsDelay() (gas: 366663)\n[PASS] testRewardsAccrueAtSwapAndConserve() (gas: 1516186)\n[PASS] testUnauthorizedNotificationInvalidAmountsAndExcessExit() (gas: 117319)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 139.87ms (123.98ms CPU time)\n\nRan 8 tests for test/Hook.t.sol:IMDIsCurrency1Test\n[PASS] testAllFourSwapModesFeeSplitAndRedemption() (gas: 2026773)\n[PASS] testBuybackLimitsAndProtocolSlippage() (gas: 1430348)\n[PASS] testBuybackRejectsManipulatedSpotAndRollsBackCooldown() (gas: 1202346)\n[PASS] testFreshManagerTokenOnlyPoolBuy() (gas: 786823)\n[PASS] testFuzzBuyFeeAndConservation(uint96) (runs: 128, μ: 756002, ~: 757119)\nLogs:\n  Bound result 2170665076\n\n[PASS] testQuoteHasNoSideEffectsAndRouterBounds() (gas: 1553679)\n[PASS] testTokenAndPermissionsAndImmutableRuntime() (gas: 19549246)\n[PASS] testUnauthorizedCallbacksAndWrongPool() (gas: 235376)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 177.17ms (120.07ms CPU time)\n\nRan 1 test for test/AdversarialFlows.t.sol:FeePropertiesIMD1Test\n[PASS] testFuzz_EachOrderChargesOnlyIMDAndConservesSplit(bool,bool,uint96) (runs: 1000, μ: 1202475, ~: 1219616)\nLogs:\n  Bound result 56489783828\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 460.40ms (443.89ms CPU time)\n\nRan 1 test for test/AdversarialFlows.t.sol:FeePropertiesIMD0Test\n[PASS] testFuzz_EachOrderChargesOnlyIMDAndConservesSplit(bool,bool,uint96) (runs: 1000, μ: 1201927, ~: 1219615)\nLogs:\n  Bound result 8311555573886180108736\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 460.51ms (431.84ms CPU time)\n\nRan 13 tests for test/NFT.t.sol:NFTTest\n[PASS] testBurnExactShareDustAndHolderOnly() (gas: 1702072)\n[PASS] testERC721CallbackCannotReenterPayments() (gas: 580657)\n[PASS] testEverMintedCapAndNeverReuseBurnedId() (gas: 396822950)\n[PASS] testExactEthSplitCreditAndFlush() (gas: 1044795)\n[PASS] testForcedEthOnlyGoesToFixedRecipients() (gas: 190257)\n[PASS] testFuzzBurnConservation(uint96,uint96,uint8) (runs: 128, μ: 2482396, ~: 1957258)\nLogs:\n  Bound result 1\n  Bound result 11198437332701793709803\n  Bound result 4377\n\n[PASS] testGasExhaustionCreatesRetryableCredit() (gas: 632709)\n[PASS] testNoWithdrawalOrAdminSelectors() (gas: 287314)\n[PASS] testOnePerTransactionAcrossCallersAndReferenceBoundary() (gas: 806971)\n[PASS] testProductionDifficultyAndGenesis() (gas: 5369371)\n[PASS] testRetargetHarderEasierAndSaturation() (gas: 6085527)\n[PASS] testRollingWindowNotCalendarHour() (gas: 15211615)\n[PASS] testWrongPriceReferenceSeedProofAndSenderBinding() (gas: 691210)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 460.51ms (444.54ms CPU time)\n\nRan 5 tests for test/NFTFailureProperties.t.sol:NFTFailurePropertiesTest\n[PASS] testFuzz_RetargetUsesEightMintElapsedTimeWithBothClamps(uint16) (runs: 1000, μ: 1912716, ~: 1913144)\nLogs:\n  Bound result 480\n\n[PASS] testFuzz_WrongPriceNeverConsumesProofOrPaysRecipients(uint64) (runs: 1000, μ: 411526, ~: 411614)\nLogs:\n  Bound result 9850\n\n[PASS] test_ETHRecipientCannotReenterMintPaymentsOrBurn() (gas: 593875)\n[PASS] test_ProofCannotBeReusedWithDifferentReferenceHash() (gas: 392557)\n[PASS] test_RejectingNFTReceiverRollsBackProofSeedLatchAndPayments() (gas: 856612)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 460.54ms (915.09ms CPU time)\n\nRan 11 tests for test/AdversarialFlows.t.sol:AdversarialFlowsTest\n[PASS] testFuzz_ClaimFrequencyPreservesFractionalEntitlement(uint96,uint96,uint96) (runs: 1000, μ: 4651249, ~: 4745436)\nLogs:\n  Bound result 116490282825834726\n  Bound result 162514264337593543950334\n  Bound result 463091\n\n[PASS] test_AllClaimReceiversRejectSpoofedAndUnsolicitedUnlocks() (gas: 1275842)\n[PASS] test_BuybackFailuresRestoreCooldownClaimsAllowanceAndPool() (gas: 2661137)\n[PASS] test_BuybackThenBurnRedeemsFreshClaimsAndLeavesNoDust() (gas: 1744993)\n[PASS] test_CommonAdminAndWithdrawalCallsCannotReleaseFunds() (gas: 3447731)\n[PASS] test_FailedStakeCannotCreateActivePrincipalOrRewards() (gas: 1258318)\n[PASS] test_FailedSwapRestoresPoolClaimsRewardsAndBalances() (gas: 1849240)\n[PASS] test_ImmutableRecipientsAndCompanionsMatchTheLaunch() (gas: 164461)\n[PASS] test_QuotesBothWaysCannotAccrueRewardsOrConsumeFunds() (gas: 1743629)\n[PASS] test_RouterAndHookRejectIntegerBoundariesBeforeAccruing() (gas: 669659)\n[PASS] test_WrongPoolRejectedInBothSwapCallbacks() (gas: 728192)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 460.70ms (451.81ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantFeeConservationAndNoRecipientSubstitution\n[PASS] invariantPrincipalAndPendingRewardsStaySolvent\n AccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 356)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| PondHandler | advance     | 267   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | buyback     | 260   | 116     | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | claim       | 260   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | redeem      | 258   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | requestExit | 255   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | stake       | 252   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | swap        | 254   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| PondHandler | unstake     | 242   | 240     | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 23183\n  Bound result 150\n  Bound result 9819\n  Bound result 4\n  Bound result 5445369151\n  Bound result 7705\n  Bound result 9999999999900003072\n  Bound result 173740516\n  Bound result 8715\n  Bound result 4602\n  Bound result 9999999999900009789\n  Bound result 68776675177689604\n  Bound result 9999999999900016012\n  Bound result 9999999999900000040\n  Bound result 5709\n  Bound result 7896\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 533.91ms (513.71ms CPU time)\n\nRan 2 tests for test/NFTStateful.t.sol:NFTStatefulTest\n[PASS]\nNFTStatefulTest invariants:\n[PASS] invariant_ETHIsPaidOrBacksCredits\n[PASS] invariant_MintHistoryAndOwnershipAgree\n[PASS] invariant_VaultPaysOnlyExactBurnShares\n NFTStatefulTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭--------------------+-------------------+-------+---------+----------╮\n| Contract           | Selector          | Calls | Reverts | Discards |\n+=====================================================================+\n| NFTSequenceHandler | advance           | 3625  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | burn              | 3650  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | donate            | 3680  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | flush             | 3581  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | forceETH          | 3652  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | mint              | 3704  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | recipientFailures | 3636  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | transferFrog      | 3613  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| NFTSequenceHandler | unauthorizedBurn  | 3627  | 0       | 0        |\n╰--------------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 101\n  Bound result 17\n  Bound result 14583\n  Bound result 6192\n  Bound result 1\n  Bound result 7271481145290\n  Bound result 967988931331947347\n  Bound result 3101934468\n  Bound result 11845\n  Bound result 709\n  Bound result 6063\n  Bound result 25\n  Bound result 1730\n  Bound result 2827\n  Bound result 494\n  Bound result 4229\n  Bound result 4221\n  Bound result 6291\n  Bound result 439502350927341583329\n  Bound result 8723\n  Bound result 33\n  Bound result 662\n  Bound result 2637\n  Bound result 44323564695798057205\n  Bound result 8739864228851997\n  Bound result 1274\n  Bound result 549210301339\n  Bound result 1987\n  Bound result 34\n  Bound result 1598\n  Bound result 16037\n  Bound result 1431\n  Bound result 913292953051599776713\n  Bound result 8551\n  Bound result 8998\n  Bound result 5963\n  Bound result 4645\n  Bound result 19\n  Bound result 784621733735364054790\n  Bound result 1018\n  Bound result 1259352356\n  Bound result 7314\n  Bound result 589422308808965796\n  Bound result 6064\n  Bound result 13101\n  Bound result 349\n  Bound result 7837\n  Bound result 4135\n  Bound result 16772019458\n  Bound result 67\n  Bound result 256\n  Bound result 33075897719025\n  Bound result 2111607523174845019\n  Bound result 22\n  Bound result 1\n  Bound result 5030\n  Bound result 4090335564\n  Bound result 3435\n  Bound result 10713\n  Bound result 2176\n  Bound result 255\n\n[PASS] test_HandlerExercisesFullWindowBurnTransferAndRetry() (gas: 19715748)\nLogs:\n  Bound result 101\n  Bound result 17\n  Bound result 3599\n  Bound result 1\n  Bound result 101\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.31s (9.31s CPU time)\n\nRan 1 test for test/SystemStateful.t.sol:SystemStatefulTest\n[PASS]\nSystemStatefulTest invariants:\n[PASS] invariant_FeesAreBackedAtTheirFixedDestinations\n[PASS] invariant_FixedSupplyCannotEscapeTrackedAccounts\n[PASS] invariant_StakingPrincipalAndRewardsStaySolvent\n SystemStatefulTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------------+-------------+-------+---------+----------╮\n| Contract              | Selector    | Calls | Reverts | Discards |\n+==================================================================+\n| SystemSequenceHandler | advance     | 2433  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | burn        | 2489  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | buyback     | 2530  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | claim       | 2407  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | donate      | 2478  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | redeem      | 2414  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | requestExit | 2510  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | stake       | 2435  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | swap        | 2434  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | unstake     | 2446  | 0       | 0        |\n╰-----------------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 100000000000000000000\n  Bound result 3000000000000000000\n  Bound result 7000000000000000000\n  Bound result 7000000000000000000\n  Bound result 100000000000000000000\n  Bound result 13336\n  Bound result 8013\n  Bound result 1752\n  Bound result 5278\n  Bound result 8989\n  Bound result 5957\n  Bound result 17331\n  Bound result 99\n  Bound result 9332\n  Bound result 12074508612232243465\n  Bound result 16254\n  Bound result 11545\n  Bound result 246\n  Bound result 9771077950835744212517\n  Bound result 1364505725\n  Bound result 130158510223707\n  Bound result 7821\n  Bound result 1834\n  Bound result 2136636540\n  Bound result 8177\n  Bound result 11740\n  Bound result 132892\n  Bound result 62785\n  Bound result 1967\n  Bound result 139249071089094\n  Bound result 5882\n  Bound result 5094\n  Bound result 524\n  Bound result 12218\n  Bound result 17489\n  Bound result 2670\n  Bound result 6173\n  Bound result 10066\n  Bound result 11276\n  Bound result 11744\n  Bound result 16212\n  Bound result 113549767\n  Bound result 5863\n  Bound result 0\n  Bound result 23008269867626546136\n  Bound result 1377\n  Bound result 6621\n  Bound result 15840\n  Bound result 84\n  Bound result 2887\n  Bound result 8588126\n  Bound result 6113135369971506320\n  Bound result 29\n  Bound result 1300\n  Bound result 13302\n  Bound result 1\n  Bound result 8900\n  Bound result 584\n  Bound result 80239323052055106311\n  Bound result 15911\n  Bound result 11612\n  Bound result 16384\n  Bound result 127149\n  Bound result 48101525805993845031\n  Bound result 12568865\n  Bound result 4354\n  Bound result 11118\n  Bound result 33532315571672592953\n  Bound result 79646124576677666832\n  Bound result 5\n  Bound result 12023\n  Bound result 0\n  Bound result 2999999998635426303\n  Bound result 9771077950835744238816\n  Bound result 2360\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.44s (10.42s CPU time)\n\nRan 15 test suites in 10.44s (23.12s CPU time): 68 tests passed, 0 failed, 1 skipped (69 total tests)\n","passed":true},{"durationMs":88,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FrogRouter.quote(bool,uint256,uint160)\",\"FrogRouter.swap(bool,uint256,uint256,uint160,uint256)\",\"FrogRouter.unlockCallback(bytes)\",\"FrogStaking.claim()\",\"FrogStaking.notifyReward(uint256)\",\"FrogStaking.redeemFees()\",\"FrogStaking.requestUnstake(uint256)\",\"FrogStaking.stake(uint256)\",\"FrogStaking.unlockCallback(bytes)\",\"FrogStaking.unstake()\",\"HFROG.approve(address,uint256)\",\"HFROG.transfer(address,uint256)\",\"HFROG.transferFrom(address,address,uint256)\",\"HashFrog.approve(address,uint256)\",\"HashFrog.burn(uint256)\",\"HashFrog.buyback(uint256,uint256,uint160,uint256)\",\"HashFrog.flush()\",\"HashFrog.mine(uint256,bytes32,uint256)\",\"HashFrog.redeemFees()\",\"HashFrog.safeTransferFrom(address,address,uint256)\",\"HashFrog.safeTransferFrom(address,address,uint256,bytes)\",\"HashFrog.setApprovalForAll(address,bool)\",\"HashFrog.transferFrom(address,address,uint256)\",\"HashFrog.unlockCallback(bytes)\",\"HashFrogHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"HashFrogHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"HashFrogHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"HashFrogHook.redeemFees()\",\"HashFrogHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"README.md\":124,\"dependencies.json\":43,\"docs/DEPLOYMENT.md\":53,\"docs/SECURITY.md\":48,\"docs/validation.json\":59,\"docs/vendor-sha256.json\":512,\"foundry.toml\":24,\"launch.parameters.json\":34,\"package.json\":1,\"script/MainnetRehearsal.s.sol\":90,\"script/PrepareLaunch.s.sol\":31,\"site/abi.json\":1,\"site/app.js\":206,\"site/config.json\":6,\"site/index.html\":27,\"site/miner/common.js\":29,\"site/miner/keccak.wasm\":12,\"site/miner/keccak.wat\":201,\"site/miner/keccak.wgsl\":116,\"site/miner/worker.js\":98,\"site/pond.svg\":1,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/ClaimReceiver.sol\":60,\"src/FrogArt.sol\":100,\"src/FrogOracle.sol\":76,\"src/FrogRouter.sol\":115,\"src/FrogStaking.sol\":124,\"src/HFROG.sol\":11,\"src/HashFrog.sol\":205,\"src/HashFrogHook.sol\":174,\"src/HookFlags.sol\":28,\"test/Admission.t.sol\":36,\"test/AdversarialFlows.t.sol\":395,\"test/Hook.t.sol\":220,\"test/Invariant.t.sol\":126,\"test/MainnetFork.t.sol\":25,\"test/NFT.t.sol\":406,\"test/NFTFailureProperties.t.sol\":165,\"test/NFTStateful.t.sol\":286,\"test/Oracle.t.sol\":50,\"test/REVIEW.md\":86,\"test/Staking.t.sol\":137,\"test/SystemStateful.t.sol\":320,\"test/helpers/Create2Deployer.sol\":10,\"test/helpers/Fixture.sol\":107,\"test/mocks/MockERC20.sol\":13,\"test/site/integration.mjs\":69,\"test/site/miner.test.mjs\":32,\"test/site/static.test.mjs\":20,\"tools/build-miner.mjs\":7,\"tools/configure-site.mjs\":30,\"tools/export-abi.py\":5,\"tools/generate-miner.py\":92,\"tools/vendor/linkedom.LICENSE\":15,\"tools/vendor/linkedom.js\":12761,\"tools/vendor/wabt.LICENSE\":202,\"tools/vendor/wabt.cjs\":38},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ed0dac235254dab95a0ed1a13e109f6b9bed8bbea6cb28799b70493643e2156e","verifiedTreeHash":"09296d0d67b09505e5fd7cf81909e89fa83e3e31","verifierVersion":"0.1.0+be003835"}]}