{"assessments":[],"deployments":[{"attestationHash":"55415cfdbe2188caa1efa6a3c7b61e18a91c451ec578e9c89dff23312a2bda04","chainId":11155111,"contracts":[{"address":"0xfaeb5e0d12370b36b767e7d00fd67f107d75d0cc","blockNumber":12049060,"name":"TidalHook","role":"hook","txHash":"0x33e414689de56937f0eef2b8eb25a7862ec402163610eea19b5e809e731920f7"},{"address":"0x0ec84c246d68384b40eba3646aea62612ed984ac","blockNumber":12049060,"name":"TidalToken","role":"token","txHash":"0x33e414689de56937f0eef2b8eb25a7862ec402163610eea19b5e809e731920f7"},{"address":"0x7d0cf3d72ee0026f28c52fc564f751c282477060","blockNumber":12049060,"name":"MerkleDistributor","role":"distributor","txHash":"0x33e414689de56937f0eef2b8eb25a7862ec402163610eea19b5e809e731920f7"}],"id":"1beeda68-b984-484a-9b11-927ebaf6b1a6","manifestHash":"5623a8b179d403e9f3c5fcb5e7ac52f2b0fbc18594d6a382b75c3f51020b2cf9","status":"live"}],"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":"bd7b13f7-b5a2-4d90-b022-35c6edd04d70","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"4ea53eb49943c04ea66831001b5e695b0e548b173409eedd8f9094ea046e1d5e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"e9ecd67becda2bf4b5cd9840c857206d7f8b24035f14fb6f79c7308d21541724","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3d994bb3680bc00bb03e76af5a5aa5f08e840bfb1083ada2fa2ec41a4b9bb55a","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"50df6bbfa8d189892a60ebb419a28febf2897a82145fb9a827b20edef170d1c8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"c70cfd35d7376eab0d4c9be002602c66b0d5669d7f788dc89d674390a8f6d971","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"5120c3c9ee81ecd2f2349b520be0c6280e6a5c1150e0cb1fe6c788046b8bc407","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"0553d0f1b6da25d2d4a1014c1f18dacbe31fb84acb858797088982c6ff6c33d5","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"989927e87f44624b1aadd15b4c97147dbaafc0b3e24e2b62d0af064e16f50ca9","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Dynamic fee hook with anti-sniper protection and buyback-burn.\n1) Base fee is 0.30%. The fee rises automatically with short-term volatility (measured from price change over the last few blocks), up to a cap of 2.00%, and decays back to base when the market calms down.\n2) For the first 20 blocks after the pool opens, an extra launch fee starts at 20% and decays linearly to 0, to discourage sniper bots.\n3) Of every fee collected, 50% goes to the pool creator's treasury and 50% is used to buy back the token and burn it.\n\nToken name: Tidal\nToken symbol: TIDAL","parentJobId":null,"planHash":"ba86e6d0b8843f838207f9a92e6bcf4e7db3dc7cecece7bd100ba6a74b0b0af2","previousHash":"61bdbd4e93165ad878354d43f1304607b9c3004cb80b47aea37d9489cb684c91","projectId":"bd7b13f7-b5a2-4d90-b022-35c6edd04d70","publication":{"commit":"e60d6360b79d69d3bfb3fcb7009fb84d85b65f50","deliveredAt":"2026-11-02T15:30:21.865Z","repoUrl":"https://github.com/identity-md-launches/launch-2880-tidal"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"59867510dbf6e12047fcdd603f2836170995a0d28d50580fb1d614ee3eb858fc","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"e56fae40c5e7b30977b0a020c3b2d6d69d8ad91ca16cafb85cc20049531e538c","device":"a68191e8efe5b6e4","findings":[],"hash":"0553d0f1b6da25d2d4a1014c1f18dacbe31fb84acb858797088982c6ff6c33d5","nodeId":"a3db2afb-50a5-4b79-8de4-d9538d31aafc","outcome":"completed","summary":"Created [launch.json](/home/imd/.identitymd-6/work/bd7b13f7-b5a2-4d90-b022-35c6edd04d70/a3db2afb-50a5-4b79-8de4-d9538d31aafc/launch.json) matching the contracts, constructor arguments, permissions and required pool settings. Notes clarify owner-operated buybacks.\n\nManifest checks passed. `forge build` succeeded; all 57 tests passed with the required fuzz seed. Only `launch.json` is changed for submission.","treeHash":"60718a76f7de6926fd1daafb8fbea2100ab7ce2a","usage":{"cachedInputTokens":471040,"inputTokens":66632,"model":"gpt-6-astra","outputTokens":4111,"runtime":"codex","turns":3,"wallClockMs":177100}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e02ee782fe2a91c3","findings":[{"citation":"resolved","description":"setTreasury (src/TidalHook.sol:248-257, onlyOwner) rewrites the destination of value already credited in treasuryAccrued without paying it out, while claimTreasury (src/TidalHook.sol:261-271) is callable by anyone and always pays the address stored in `treasury` at execution time. The README states the owner may change the recipient 'including for already accrued treasury fees'. When the owner broadcasts setTreasury(next) to move accrued fees away from a mis-set, lost or compromised recipient, any observer can land claimTreasury() first and irreversibly deliver the full accrued balance to the old recipient. There is no atomic switch-and-pay path and no owner-only claim. Reported by audit_permissions; reproduced. Low because with an honest previous recipient the creator still receives the funds; the loss requires the old treasury to be an address the creator no longer wants paid. Minimal fix preserving the design: pay treasuryAccrued to the old recipient (or to the new one, by owner choice) inside setTreasury before updating `treasury`, or restrict claimTreasury to owner/treasury.","line":266,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool (ETH/TIDAL, liquidity 1000e18 in [-60000,60000], price 1:1), launch block, owner = creator. Owner calls setTreasury(STALE). Exact-input buy of 10 ether at the 20.30% launch rate: treasuryAccrued == 1.015 ether. Owner intends setTreasury(INTENDED); stranger calls claimTreasury() first, then setTreasury(INTENDED) executes. Expected (per README): 1.015 ether reaches INTENDED. Actual: STALE.balance == 1.015 ether, INTENDED.balance == 0, treasuryAccrued == 0. Reproduced in test/scratch/Repro.t.sol::test_treasuryRace (log 'treasuryAccrued 1015000000000000000').","severity":"low","snippet":"        address recipient = treasury;","title":"setTreasury does not settle accrued fees first, and permissionless claimTreasury lets anyone front-run a recipient change to pay the whole accrued balance to the previous address"},{"citation":"resolved","description":"_observe() adds the absolute tick distance of every swap into the current block's bucket (saturating at MAX_BUCKET_TICKS = 340) and dynamicFeePips() weights that bucket 8/8 in the same block, decaying 1/8 per block. A trader who buys and immediately sells back in one block moves the tick out and back (both legs count; net price change ~0), saturates the bucket and makes every later swap in that block and the following 7 blocks pay up to 20000 pips instead of 3000, although no price movement persists. The brief describes the signal as 'price change over the last few blocks'; the implementation measures traversed distance, which an unprivileged party can set at will. The attacker pays LP and hook fees on both legs, so this is griefing of identifiable victims (every trader in the window), not profit; the README documents the signal as manipulable at a trading cost. Reported by audit_economics and audit_flow (same root cause, merged). Severity low: bounded loss (max 2.00% vs 0.30%), attacker pays more than any single victim, and the behaviour is documented. If the requester wants the signal to resist this, use open-to-close tick change per block (net movement) or weight the current block less.","line":368,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, 21 blocks after launch (launchFeePips()==0, quoteFeePips()==3000). Same block: (1) exact-input buy of 40 ether -> tick moves -773 (bucket saturates at 340), quoteFeePips()==20000; (2) sell back the exact TIDAL received -> tick returns to -10, quoteFeePips() still 20000. Victim exact-input buy of 1 ether in the same block: hook fee collected == 0.02 ether (expected at base 0.003 ether, 6.7x). Quote in the next blocks: 17875, 15750, 13625, 11500, 9375, 7250, 5125, then 3000 at age 8. Reproduced in test/scratch/Repro.t.sol::test_roundTripPinsFee (logs 'tick move -773', 'quote after round trip 20000').","severity":"low","snippet":"        uint256 movement = uint256(difference < 0 ? -difference : difference);","title":"Volatility signal is cumulative absolute tick path length, so one same-block round trip with no net price change pins the fee at the 2.00% cap for every trader for the next 8 blocks"},{"citation":"resolved","description":"The brief says 50% of every fee 'is used to buy back the token and burn it'. The implementation only accrues that half into buybackAccrued on each swap; the sole function that decreases buybackAccrued is executeBuyback, which is onlyOwner and needs an owner-chosen price limit, minimum output and deadline. There is no keeper path, no delayed permissionless execution and no sweep; renounceOwnership is disabled but a lost key has the same effect, and the only outflow is a swap in this one pool, so if liquidity leaves, the budget cannot be spent at all. Treasury payouts, by contrast, are permissionless. This is documented in the README as a trust assumption ('Lost owner access can leave buybacks queued indefinitely') and the owner cannot withdraw the funds, so it is a design risk, not a permission bypass. Reported by audit_economics and audit_flow (merged). Severity low: no third party can take the funds; the deviation is that the brief's 'is used to buy back' depends entirely on an active owner. If the requester wants a guarantee, a permissionless execution after N blocks with an owner-set or on-chain-bounded price limit would remove it without changing the economics.","line":286,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, launch block. Exact-input buy of 5 ether at 20.30%: buybackAccrued == 0.5075 ether. (a) stranger calls executeBuyback(0.5075e18, 1, TickMath.MIN_SQRT_PRICE+1, block.timestamp) -> reverts OwnableUnauthorizedAccount(stranger). No other entry point reads buybackAccrued (grep: only executeBuyback writes it). (b) router removes all liquidity (modify -1000e18), owner calls executeBuyback with the same arguments -> reverts NoTrade; buybackAccrued remains 0.5075 ether. Expected per brief: the buyback half is used for burns; actual: it stays in the hook unless an owner transacts and liquidity exists. Reproduced in test/scratch/Repro.t.sol::test_buybackStranded (log 'buybackAccrued 507500000000000000').","severity":"low","snippet":"    ) external nonReentrant onlyOwner returns (uint256 spent, uint256 burned) {","title":"The buyback half of every fee can only ever be spent by an owner transaction; no permissionless or time-based fallback, so an inactive or lost owner strands 50% of all fees indefinitely"},{"citation":"resolved","description":"afterSwap applies quoteFeePips() = dynamicFeePips() + launchFeePips() regardless of params.zeroForOne. The brief motivates the launch fee as anti-sniper protection; snipers are buyers in the first blocks. Any holder who received TIDAL at launch (the distributor and payer allocations) and sells within the first 20 blocks pays the same surcharge on their ETH output. The README documents the combined 20.30% first-swap rate without distinguishing direction, so this may be intended (it also deters snipers from flipping). Reported by audit_economics; reproduced. Informational: the requester should confirm the intent; if sells were meant to be exempt, add launchFeePips() only when params.zeroForOne in both beforeSwap and afterSwap.","line":217,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, same block as initialization (age 0), quoteFeePips()==203000. Exact-input sell of 1 ether TIDAL (zeroForOne=false): pool pays out 0.986525805766805279 ETH gross; hook takes 0.200264738570661471 ETH (20.3%); seller receives 0.786261067196143808 ETH. Expected if the surcharge targets only buyers: fee = 0.3% = ~0.00296 ETH. Reproduced in test/scratch/Repro.t.sol::test_launchFeeOnSell (logs 'seller net eth 786261067196143808', 'fee 200264738570661471').","severity":"info","snippet":"        uint24 rate = quoteFeePips();","title":"Launch surcharge (20% decaying) is charged on sells as well as buys, so early legitimate sellers forfeit up to 20.3% of their ETH proceeds"},{"citation":"resolved","description":"When ETH is the specified side the hook pre-charges the fee via beforeSwapReturnDelta and afterSwap requires the pool to have consumed/produced exactly the adjusted amount. If the trader's sqrtPriceLimitX96 is hit first or liquidity runs out, the pool fills partially and the whole swap reverts with NativeSpecifiedNotFilled instead of settling the partial fill. The README documents this as deliberate (v4 lets afterSwap adjust only the unspecified side). Reported by audit_flow; reproduced. Informational: a behavioural difference routers and aggregators must know about, since a real price limit used for slippage protection on ETH-specified trades produces hard reverts rather than partial fills.","line":234,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, 21 blocks after launch. router.swap{value: 50 ether}(key, SwapParams(true, -50 ether, TickMath.getSqrtPriceAtTick(-10)), 0) -> entire transaction reverts (NativeSpecifiedNotFilled), zero output, totalFeesCollected unchanged. The same trade token-specified, SwapParams(true, +50 ether, getSqrtPriceAtTick(-10)), fills partially: ETH in 0.507954252631819520, TIDAL out < 50 ether, fee charged on the actual ETH. Reproduced in test/scratch/Repro.t.sol::test_partialNativeFillReverts and by the existing test/FeeFlows.t.sol::test_partialNativeSpecifiedBuyRevertsWithoutCharge.","severity":"info","snippet":"            if (nativeAmount != expected) revert NativeSpecifiedNotFilled();","title":"ETH-specified swaps (exact-input buys, exact-output sells) revert on any partial fill, unlike the token-specified paths of the same pool and unlike unhooked v4 pools"}],"hash":"3d994bb3680bc00bb03e76af5a5aa5f08e840bfb1083ada2fa2ec41a4b9bb55a","nodeId":"389ecc5d-45b7-4ed8-a3d6-5a609714a8b5","outcome":"completed","summary":"The review is complete. The findings file is written and validated, the scratch directory is removed, and no tracked file was changed.\n\n**Outcome:** five findings kept, none above low severity. No loss-of-funds or deployment-blocking defect was found. The build compiles and the full existing suite passes (86 tests, two invariant suites with zero unexpected reverts).\n\n**Findings kept, each reproduced with a Foundry test against the real PoolManager:**\n\n- **Low, setTreasury race.** The owner's recipient change does not settle accrued fees first, and claimTreasury is permissionless, so a stranger can front-run the change and pay the whole accrued balance to the old address. Reproduced: 1.015 ETH paid to the stale address, zero to the intended one.\n- **Low, volatility signal pinning.** The fee signal is cumulative absolute tick path length, so one same-block round trip with no net price change pins every trader's fee at the 2.00% cap for 8 blocks. Reproduced: a 40 ETH buy and sell-back left the tick at -10 but the quote at 20000 pips, and a victim paid 0.02 ETH on a 1 ETH buy instead of 0.003 ETH. Two specialists reported this and I merged them.\n- **Low, owner-dependent buybacks.** The buyback half of every fee can only be spent by an owner call to executeBuyback, with no fallback. Reproduced: a stranger is refused, and with liquidity withdrawn the owner's call reverts with NoTrade, leaving 0.5075 ETH queued. Two specialists reported this and I merged them.\n- **Info, launch fee on sells.** The 20% decaying surcharge applies to sells too. Reproduced: a 1 TIDAL sell in the launch block paid 0.2003 ETH of 0.9865 ETH gross.\n- **Info, ETH-specified partial fills revert.** Exact-input buys and exact-output sells revert on any partial fill while the token-specified paths settle partially. Reproduced against the same price limit on both modes.\n\n**Dropped:** the math specialist's dust-rounding note, which the specialist itself marked as needing no change, and the flow specialist's tick-spacing note, since launch.json already writes 60 and the fee 12500 matches the hook's hard-coded key.\n\n**Coverage:** all 14 listed entry points are answered, 10 holds and 4 finding, plus two invariant rows. My own pass confirmed delta accounting nets to zero in all four swap modes, the claim-mint fallback on a token-only seeded manager, self-swap callback exclusion on buybacks, permission mask 0x10cc matching getHookPermissions and the manifest, and bytecode sizes under both limits.","treeHash":null,"usage":{"cachedInputTokens":1416190,"inputTokens":54,"model":"claude-fable-5-1","outputTokens":40014,"runtime":"claude","turns":38,"wallClockMs":539275}},{"artifacts":[],"attempt":1,"bundleHash":"402f217579606dc5a859b958ff1127608d022482cebd1f916eb64100ced826f1","device":"8472a2af9a9cf974","findings":[],"hash":"46c1f356eef21bb43fd257cd5cf3a5bda182caa80773a9e4d3722336c8340a75","nodeId":"bd15d1f0-338c-4a6a-a68f-f153719087f1","outcome":"completed","summary":"Work is complete. Final state: build, tests and formatting all pass, and the protected floor tests pass against the built bytecode.\n\n**What was built**\n\n- `src/TidalToken.sol`: the launch token. Name Tidal, symbol TIDAL, 18 decimals, one billion tokens minted once to the deployer, no owner or admin functions.\n- `src/TidalHook.sol`: the pool hook. It takes the PoolManager, owner and factory as constructor arguments, written as `$poolManager`, `$owner` and `$factory` in the manifest.\n- `src/HookFlags.sol`: the small internal library the floor tests import.\n- `README.md`: fee schedule, per-swap fee math, settlement, hook configuration record, deployment parameters, chosen constants, post-launch operations, assumptions, security notes and vendored dependency commits.\n\n**How the hook meets the brief**\n\n- Base fee 0.30%, plus 17 pips per tick of movement over the last five blocks, capped at 2.00% total, decaying 0.05% per block when the market is calm.\n- Launch fee of 20% in the opening block, falling 1% per block to zero at block 20.\n- Every fee is taken on the ETH side inside the swap, for all four swap shapes. Half is owed to the treasury and pulled with a permissionless claim. The other half buys TIDAL in the same pool at once and sends it to the dead address, since the launch token may not have a burn function.\n- When the PoolManager holds no ETH yet, the hook mints itself an ERC-6909 claim instead of reverting, and the same fallback exists for bought TIDAL the manager does not physically hold yet.\n- Only the factory can open the pool, once, ETH-paired, with a static LP fee. No liquidity callbacks, so the factory's fee collect passes untouched.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| forge build | passes, hook 13.8 KB |\n| forge test with the verifier's fuzz seed | 50 passed, 0 failed |\n| forge fmt --check | clean |\n| Protected floor tests run from scratch with the verifier's env | 16 passed, 0 failed |\n\n**Two things worth knowing**\n\n- The sandbox made any directory containing a `.gitmodules` file read-only, so the dependencies live in `deps/` rather than `lib/`. Three empty leftover folders under `lib/` could not be deleted and are excluded by `.gitignore`. Foundry is pointed at `deps/` and nothing references `lib/`.\n- I also provided the support files the floor tests import, under `test/mocks/`, `test/floor/` and `src/HookFlags.sol`, so the floor compiles whether or not the verifier ships its own copies.\n\n**What remains outside this task**\n\n- The manifest step writes `launch.json` after acceptance.\n- The hook moves user funds inside swaps and needs an independent adversarial review before launch, as the README states.","treeHash":"3224a259cb4f684f9f00663d951e8cf0bfe19502","usage":{"cachedInputTokens":4411705,"inputTokens":962,"model":"claude-fable-5-1","outputTokens":115364,"runtime":"claude","turns":72,"wallClockMs":1574828}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"27168b1ff68f84d2","findings":[{"citation":"resolved","description":"Economic Security / manipulable fee input. _observe() adds the absolute tick distance of every swap (user or buyback) into the current block's bucket, saturating at 340 ticks, and dynamicFeePips() weights that bucket 8/8 in the same block, decaying 1/8 per block. A single trader who buys and immediately sells back in one block moves the tick there and back (path length counts both legs, net price change is ~0), which puts the bucket at the 340-tick saturation and makes every other swap in that block pay the 2.00% cap instead of 0.30%, then 1.79%, 1.58% ... down to 0.30% over the next 8 blocks, with no price movement actually persisting. The signal is therefore not 'price change over the last few blocks' as the brief words it, but cumulative movement, and it can be set by an unprivileged party at will. The attacker pays LP and hook fees on both legs so the vector is griefing, not profit, but the victims are identifiable (every trader in the window) and the loss is material relative to the quoted base fee. The README documents the signal as manipulable; this report quantifies it so the requester can accept it knowingly or switch to a net-change (open-to-close tick) or TWAP-style measure.","line":368,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool (ETH/TIDAL, liquidity 1000e18 in [-60000,60000], price 1:1), 21 blocks after launch so launchFee=0 and quoteFeePips()==3000. Attacker, same block: (1) router.swap exact-input buy -40 ether -> tick moves -773, quoteFeePips()==20000; (2) sell back exactly the TIDAL received -> tick returns near start, quoteFeePips() still 20000. Attacker net cost 1.870 ETH (LP fee + hook fee on both legs). Victim then buys with exact-input 1 ether in the same block: hook fee collected = 0.020000 ETH (expected at base 0.003 ETH, i.e. 6.7x). Subsequent blocks with no trading quote 17875, 15750, 13625, 11500, 9375, 7250, 5125, then 3000 at +8. Reproduced with forge test in test/scratch (Fixture-based), logs: 'fee quoted after round trip (pips) 20000', 'victim hook fee on 1 ETH 20000000000000000'.","severity":"low","snippet":"        uint256 movement = uint256(difference < 0 ? -difference : difference);","title":"Volatility fee signal is path-length of tick movement and can be pinned at the 2.00% cap for everyone by any trader's same-block round trip"},{"citation":"resolved","description":"Flow Gap (execution x first principles). The brief states that 50% of every fee 'is used to buy back the token and burn it'. The implementation only accrues buybackAccrued on each swap; converting it to a burn requires the owner to call executeBuyback with a price limit, minimum output and deadline. There is no permissionless, time-based or bounded-slippage fallback, renounceOwnership is disabled but a lost key has the same effect, and the only outflow of buybackAccrued is a swap in this one pool. If the owner never acts, or if liquidity leaves the pool, the ETH stays in the hook with no recovery path (no sweep, no redirect). This is an intentional trust assumption documented in the README ('Lost owner access can leave buybacks queued indefinitely'), so it is reported as a design-risk item, not a permission bypass; the judge and requester should decide whether a permissionless fallback (e.g. anyone may execute after N blocks with a TWAP-bounded limit) is required by the brief.","line":286,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, one buy of 5 ether at launch rate -> buybackAccrued = 0.5075 ETH. (a) No owner call: buybackAccrued stays 0.5075 ETH indefinitely; no other function decreases it (grep: only executeBuyback writes it). (b) Liquidity gone: router.modify(key,-60000,60000,-1000 ether) removes all liquidity, then owner calls executeBuyback(0.5075e18, 1, TickMath.MIN_SQRT_PRICE+1, block.timestamp+60) -> reverts NoTrade (hook line 325, delta.amount0()==0), budget remains stuck. Reproduced in test/scratch: 'stuck buyback budget 507500000000000000'. Expected per brief: the buyback share is used for burns; actual: it depends entirely on an active owner and live liquidity.","severity":"low","snippet":"    ) external nonReentrant onlyOwner returns (uint256 spent, uint256 burned) {","title":"Buyback half of every fee is only ever spent by an owner transaction; no fallback path, so 50% of fees can sit in the hook forever (lost key, inactive owner, or pool with no liquidity)"},{"citation":"resolved","description":"Economic Security / incentive alignment. quoteFeePips() = dynamicFeePips() + launchFeePips() is applied in afterSwap regardless of params.zeroForOne. The brief motivates the launch fee as anti-sniper protection; snipers are buyers in the first blocks. Charging the same surcharge on sells means any holder who received TIDAL at launch (the 10% distributor allocation, 2% payer allocation per README) and sells in the first 20 blocks forfeits up to 20% of their ETH to the hook. This may be intended (it also deters snipers from flipping) and the README documents the combined 20.30% first-swap rate, so this is informational: the requester should confirm the intent. If sells were meant to be exempt, the fix is to add launchFeePips() only when params.zeroForOne.","line":217,"path":"src/TidalHook.sol","reproduction":"State: Fixture pool, same block as initialization (age 0), quoteFeePips()==203000. Sell exact-input -1 ether TIDAL (zeroForOne=false): pool pays out 0.786261067196143808 ETH gross, hook takes 0.200264738570661471 ETH (20.3% of 0.9865... gross incl. fee), seller receives the remainder. Logged in test/scratch: 'sell gross eth 786261067196143808 fee 200264738570661471'. Expected if the surcharge targets only snipers: sell fee 0.3% = ~0.00296 ETH; actual 0.2003 ETH.","severity":"info","snippet":"        uint24 rate = quoteFeePips();","title":"Launch surcharge (20% decaying) is charged on sells as well as buys, so early legitimate sellers pay up to 20.3% of their ETH proceeds"}],"hash":"4ea53eb49943c04ea66831001b5e695b0e548b173409eedd8f9094ea046e1d5e","nodeId":"24a569d5-6003-40a5-88df-f49009e7d760","outcome":"completed","summary":"The economics review of `TidalHook` and `TidalToken` is complete. The findings and a 19-row coverage record are in `.imd-findings.json` at the repository root. No files in the tree were changed, and the scratch tests I used for reproduction were removed.\n\n**Outcome: no fund-loss defect found in the assigned area.** The fee flows, 50/50 split, claim fallback, buyback settlement and volatility decay all trace correctly against the vendored v4-core, and the existing 57 tests plus both stateful invariant suites pass. I reported three economic items, each with a concrete reproduction from a Foundry run:\n\n- **Low. Volatility signal is manipulable by any trader.** The hook measures cumulative absolute tick movement, not net price change. One same-block round trip of 40 ETH in the fixture pool pinned every other swap at the 2.00% cap. The victim paid 0.020 ETH on a 1 ETH buy instead of 0.003 ETH. The fee then decayed over eight blocks with no real price change. The attacker's cost was about 1.87 ETH, so this is griefing, not profit. The README already calls the signal manipulable.\n- **Low. Buyback share has no fallback.** Half of every fee only leaves the hook through an owner-signed `executeBuyback`. With an inactive owner or a lost key the funds idle forever. With no liquidity in the pool the call reverts with `NoTrade` and the 0.5075 ETH budget in the reproduction is stuck. This is a documented trust assumption, flagged for the requester to weigh against the brief's wording.\n- **Info. Launch surcharge also hits sells.** A 1 TIDAL sell in the opening block pays 20.3% of its ETH proceeds. The brief frames the surcharge as anti-sniper, so the requester should confirm early sellers were meant to pay it.\n\n**Coverage.** All 14 listed entry points have a row. Twelve hold, and `afterSwap` and `executeBuyback` reference findings 1 and 2. Five extra rows record the invariants I verified by tracing every writer: treasury and buyback partition, assets backing liabilities, identical rate in `beforeSwap` and `afterSwap`, and the 22%/2% rate ceilings. One row dispositions the Slither and Aderyn leads as non-findings with the guard that defeats each.\n\n**What I did not reach.** I did not run a Sepolia fork or test against a real launch factory's seeding pattern, so the launch-window behaviour with the factory's actual price and range remains an assumption from the README.","treeHash":null,"usage":{"cachedInputTokens":1121413,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":49544,"runtime":"claude","turns":27,"wallClockMs":740912}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d63ea36a2b809080","findings":[{"citation":"resolved","description":"Math Precision guide, 'wrong rounding direction' and 'zero-rounding'. The two native-specified / token-specified exact-input paths compute the hook fee as floor(amount * rate / 1_000_000) (src/TidalHook.sol:198, :230 and :236 right-hand branch), so any ETH amount below ceil(1_000_000 / rate) wei yields fee == 0 and _collectFee returns at its `if (fee == 0) return;` guard without allocating anything to treasury or buyback. The exact-output paths use _grossUpFee (src/TidalHook.sol:421), which rounds up, so a 1 wei pool input is charged a 1 wei fee (100%). The README's settlement table documents floor for the two exact-input rows and ceil for the two exact-output rows, so this is intended. Impact is bounded to strictly less than 1 wei per swap: splitting any economically meaningful trade into sub-333-wei pieces costs orders of magnitude more gas than the fee avoided (1 ETH would need about 3e15 swaps), and there is no path where the truncated fee feeds a later multiplication. Reported for completeness of the rounding map; no code change is required. If the author prefers protocol-favouring rounding on every path, replace the two floor divisions with the same ceil formula (fee = (amount * rate + D - 1) / D) and keep the afterSwap `expected` recomputation identical.","line":230,"path":"src/TidalHook.sol","reproduction":"State: Fixture after _calm() (age >= 20, no recent movement) so quoteFeePips() == 3000. Input 1: exact-input buy of 333 wei ETH, router.swap(key, SwapParams(true, -333, MIN_SQRT_PRICE+1)). Expected if fees rounded up: fee = ceil(333*3000/1e6) = 1 wei. Actual: fee = floor(999000/1e6) = 0; totalFeesCollected stays 0 while the swap delivers 327 wei TIDAL (probe log 'fee after buy 333 wei: 0'). Input 2: exact-output buy of 1 wei TIDAL, SwapParams(true, 1, ...). Pool input is 3 wei ETH; fee = ceil(3*3000/997000) = 1 wei, swapper pays 4 wei (probe log 'buyOut1 amount0: -4', 'fee after buyOut 1: 1'). Input 3: exact-input sell of 1 wei TIDAL reverts with NoTrade() (selector 0xa3b36525) because the pool outputs 0 ETH, which is the intended guard. Run with forge test --match-path test/scratch/Probe.t.sol -vv (test_dustFees) on this tree.","severity":"info","snippet":"            fee = params.zeroForOne ? amount * rate / FEE_DENOMINATOR : _grossUpFee(amount, rate);","title":"Exact-input hook fee rounds down: buys and sells below 1e6/rate wei of ETH pay no hook fee, while the exact-output paths round up (documented floor/ceil asymmetry, dust only)"}],"hash":"50df6bbfa8d189892a60ebb419a28febf2897a82145fb9a827b20edef170d1c8","nodeId":"2cec8eb6-1621-4521-a337-b59c4738f084","outcome":"completed","summary":"The review is complete. The findings file is written and parses, the scratch tests are removed, and the working tree is clean apart from the ignored output file.\n\n**Outcome: no defect above info in the math area.** The fee arithmetic, casts, rounding carry, decay window and buyback settlement all hold under the Math Precision, Boundary and Numerical Gap checks. The findings file holds one info-level rounding note and eighteen coverage rows, all fourteen listed entry points answered.\n\n**What I verified, by reading and by scratch tests against the vendored PoolManager:**\n\n- **Rate bounds.** Dynamic premium is capped at 17,000 pips and the launch surcharge at 200,000, so the uint24 sum never exceeds 220,000. The first swap in the opening block quotes 20.3%, a 20 ETH shock saturates to 2.00%, and continuous trading followed by idle blocks returns to 0.30% exactly eight blocks later.\n- **Delta routing.** I traced all four settlement modes through v4's Hooks.beforeSwap and Hooks.afterSwap in the pinned core. Full-fill equality in afterSwap is exact because v4 drives the specified remainder to zero. Every narrowing cast is guarded before it happens.\n- **Split and claims.** The floor/ceil carry keeps cumulative treasury at floor(F/2) with no drift. Hook ETH plus claims always equals treasury plus buyback accruals, and manager ETH always covers outstanding claims because each claim was backed by the swapper's settled input. A claim-funded buyback followed by a treasury claim kept the accounting invariant.\n- **Buyback.** Budget, price limit, deadline and output floors are checked, unspent budget is restored, and the callback's deltas net to zero. A 5.075 ETH buyback burned the tokens it bought with every invariant intact.\n\n**The one finding (info).** Exact-input buys and sells compute the fee with floor division, so ETH amounts under about 333 wei at base rate pay no hook fee, while exact-output paths round up. The README documents this asymmetry, impact is under 1 wei per swap, and splitting a real trade to exploit it costs far more gas than it saves. Reproduction and probe logs are in the file.\n\n**Checks run.** The project suite with `forge test --offline` passed 57 tests with 0 failures before I started. My two scratch probe files ran 11 tests, of which 3 failed only on my own router budget assumptions, not on hook behaviour. The static-analysis leads on the hook (strict equalities, reentrancy, timestamp) all trace to guarded or benign paths and were not promoted to findings.","treeHash":null,"usage":{"cachedInputTokens":1444960,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":53707,"runtime":"claude","turns":29,"wallClockMs":741605}},{"artifacts":[],"attempt":2,"bundleHash":"746eb0aa0d590b7c41283e38d5ea5a42d8b93aadcef103fcd2383404df23810d","device":"2d33950b9728fd89","findings":[],"hash":"5120c3c9ee81ecd2f2349b520be0c6280e6a5c1150e0cb1fe6c788046b8bc407","nodeId":"bd15d1f0-338c-4a6a-a68f-f153719087f1","outcome":"completed","summary":"Implemented TIDAL and the hook with dynamic fees, launch protection, 50/50 allocation, and owner-operated buyback/burn. Replaced the flagged BitMath shifts.\n\nVerified: `forge build`, all **57 tests** with the supplied seed, and `forge fmt --check` pass.\n\n[README](./README.md) documents deployment and operations, including the additional 1.25% LP fee and buyback responsibilities.","treeHash":"d3ec1d7bbc70c31123f2ee57a290dc210db52393","usage":{"cachedInputTokens":1931904,"inputTokens":83538,"model":"gpt-6-astra","outputTokens":48929,"runtime":"codex","turns":7,"wallClockMs":2300509}},{"artifacts":[],"attempt":1,"bundleHash":"d5d91cdf65e684637d5193a9979ddbde3ee6b49118b54431b912133f5d74d62a","device":"78f562a53c16db4c","findings":[],"hash":"989927e87f44624b1aadd15b4c97147dbaafc0b3e24e2b62d0af064e16f50ca9","nodeId":"2f55226d-af40-497b-bdb7-d0c166887eae","outcome":"completed","summary":"Added failure-path, boundary, atomic settlement, and multi-actor invariant tests. All changes are under `test/`.\n\nValidation passed offline with the required seed: **86 tests, 0 failures, 0 skips**, including 24,576 invariant calls. `forge build` also passes.\n\nNo reproducible contract defects were found.","treeHash":"92bf63ed3e10950006892f9977a586728d4457c1","usage":{"cachedInputTokens":3519104,"inputTokens":122143,"model":"gpt-6-astra","outputTokens":33258,"runtime":"codex","turns":8,"wallClockMs":1137891}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c9743cd9807c4c3a","findings":[{"citation":"resolved","description":"Trust-gap seam (access x asymmetry, race amplifier). setTreasury (src/TidalHook.sol:248-257, onlyOwner) rewrites the destination of value that is already credited in treasuryAccrued without first settling it, and the README states the change is meant to apply to already accrued fees. claimTreasury (src/TidalHook.sol:261-271) is permissionless and always pays whatever address is in `treasury` at call time. So the moment an owner broadcasts setTreasury(next) to move accrued fees away from a recipient that is mis-set, lost, or compromised, any unprivileged observer can land claimTreasury() first and irreversibly deliver the whole accrued balance to the previous recipient. The owner has no atomic 'claim to the new recipient and switch' path and no way to pause claims. Preconditions: the previous treasury must be an address the creator no longer wants paid (an earlier owner error or key compromise), which is why this is low rather than medium: with an honest, intact previous treasury the funds still reach the creator. Minimal fix that preserves the design: make claimTreasury callable only by the owner or by the configured treasury, or have setTreasury take an explicit flag that lets the owner choose whether accrued fees follow the switch and pay them atomically inside setTreasury before updating the recipient.","line":255,"path":"src/TidalHook.sol","reproduction":"State: hook deployed and initialized (Fixture.setUp), owner = creator, treasury set by owner to address STALE (e.g. a typo or a key the owner no longer controls) via setTreasury(STALE). A buy of 10 ether exact-in (router.swap zeroForOne, amountSpecified = -10 ether) runs in block launchBlock, charging a 20.30% hook fee; treasuryAccrued becomes 1.015 ether. Owner then submits setTreasury(INTENDED). Stranger (any EOA) submits claimTreasury() with higher priority so it executes first. Expected (per README 'the owner may change the treasury recipient, including for already accrued treasury fees'): 1.015 ether reaches INTENDED after the owner's change. Actual: claimTreasury pays 1.015 ether to STALE, treasuryAccrued is 0 when setTreasury(INTENDED) executes, INTENDED.balance == 0. Demonstrated by test/scratch/TreasuryRace.t.sol::test_strangerFrontRunsTreasuryChange (asserts STALE.balance == accrued, INTENDED.balance == 0, treasuryAccrued == 0) against the current tree.","severity":"low","snippet":"        emit TreasuryChanged(treasury, next);\n        treasury = next;","title":"setTreasury does not checkpoint accrued fees, and permissionless claimTreasury lets anyone front-run a recipient change to force the accrued balance to the previous address"}],"hash":"c70cfd35d7376eab0d4c9be002602c66b0d5669d7f788dc89d674390a8f6d971","nodeId":"e2500113-9d3b-4ffe-9c55-af8dd84d3b36","outcome":"completed","summary":"Review complete. The findings file is valid JSON with one finding and 17 coverage rows, and the snippet matches the tree at the cited lines.\n\n**Outcome: the permission model holds, with one low-severity trust-gap finding.**\n\nI traced every `TidalHook` and `TidalToken` entry point against the Access Control, Trust Gap and Asymmetry guides. The callbacks are gated by `onlyPoolManager`, pool initialization additionally requires the launch factory and the exact canonical pool key, and the owner's only powers are changing the treasury recipient and executing price-bounded buybacks. No path moves ETH anywhere except to the configured treasury or into the pool for tokens that are burned in the same callback. The unlock authorization is consumed on entry and only reachable from the hook's own unlock. The 57 existing tests pass, and the delta accounting I relied on matches the vendored v4-core.\n\n**Finding (low):** `setTreasury` rewrites the recipient of already accrued fees without settling them, and `claimTreasury` is permissionless. Anyone can front-run an owner's treasury change with a claim and force the full accrued balance to the previous address. This only matters when the previous recipient is one the creator no longer wants paid, so it is a race on an owner error rather than a bypass. The scratch test under `test/scratch/TreasuryRace.t.sol` demonstrates it against the current code.\n\n**Coverage notes for the judge:**\n- All 14 listed entry points have rows. Twelve hold, two carry the finding.\n- The hook hard-codes ETH pairing, fee 12500 and tick spacing 60 in its pool key. I could not verify these against the manifest assignment, so that row is marked holds with the caveat.\n- Owner-chosen buyback bounds and the manipulable volatility signal are documented trust assumptions, not reported as defects.\n- Static analysis leads on reentrancy, strict equality and weak randomness were checked and are benign in context.","treeHash":null,"usage":{"cachedInputTokens":1157141,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":33456,"runtime":"claude","turns":33,"wallClockMs":494725}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"45aa937328087de3","findings":[{"citation":"resolved","description":"The brief says 50% of every fee 'is used to buy back the token and burn it'. The implementation only accrues that half into buybackAccrued; spending it requires executeBuyback, which is onlyOwner. There is no keeper path, no time-delayed permissionless execution, no owner-independent sweep, and renounceOwnership is disabled, so the only way the buyback allocation is ever spent is an owner-signed transaction. If the owner key is lost or the owner simply never acts, the buyback half of every fee collected over the pool's life sits in the hook (as ETH or ERC-6909 claims) permanently; it cannot be claimed as treasury either because treasuryAccrued and buybackAccrued are segregated. The treasury half, by contrast, is permissionless via claimTreasury. This is a documented trust assumption in the README, but it is a deviation from the requested behaviour ('is used to buy back') and a permanent-stranding condition that a simple permissionless fallback (e.g. anyone may execute after N days with a conservative on-chain bound, or owner-set bounds that anyone may execute) would remove without changing the economics.","line":286,"path":"src/TidalHook.sol","reproduction":"State: pool initialized and seeded; one buy of 1 ETH exact input at base fee (3000 pips) -> totalFeesCollected = 3e15, buybackAccrued = 1.5e15. Call from any non-owner: executeBuyback(1.5e15, 1, TickMath.MIN_SQRT_PRICE+1, block.timestamp) -> reverts OwnableUnauthorizedAccount. There is no other entry point that reads buybackAccrued; claimTreasury pays only treasuryAccrued. Expected per brief: the buyback half is spent on buy-and-burn. Actual: it remains in the hook indefinitely unless the owner transacts.","severity":"low","snippet":"    ) external nonReentrant onlyOwner returns (uint256 spent, uint256 burned) {","title":"Buyback half of every fee depends on a single owner transaction; no permissionless fallback, so an inactive or lost owner strands 50% of all fees forever"},{"citation":"resolved","description":"The dynamic fee is driven by absolute tick movement with per-block saturation at 340 ticks (about a 3.4% price move) and a weight of 8 for the current block. A single swap that moves the price 340 ticks (or two smaller same-block swaps summing to 340 ticks) sets premium = 340*8*50/8 = 17000 pips, i.e. the full 2.00% cap, in the same block, and the fee stays above base for the next 7 blocks. Because the observation is written in afterSwap of the mover and read in beforeSwap of every later swap, an attacker whose transaction lands first in a block can make every other trader in that block and the following blocks pay up to 6.7x the base fee. The attacker gains nothing directly (the fee goes to treasury/buyback), so this is griefing, but the cost is one round-trip of about 1.7% of in-range liquidity plus LP and hook fees, which on a launch pool of ~20 ETH market cap is a fraction of an ETH. The README documents the signal as 'manipulable at a trading cost'; the finding records the concrete magnitude so the author can decide whether a lower current-block weight, a per-swap cap, or a one-block lag is wanted.","line":174,"path":"src/TidalHook.sol","reproduction":"Observed in a scratch Foundry run against the real PoolManager: pool at tick 223338 seeded token-only with liquidity 1e20; block = launch block (launch fee 200000). Swap A: buy exact input 1 ETH -> quote before = 203000 pips, fee 0.203 ETH; the swap moves the price far beyond 340 ticks. Swap B in the same unlock/block: sell the 7.05e24 TIDAL received -> hook charged 0.173 ETH on a 0.787 ETH gross output = 22.0% = 200000 launch + 20000 (capped dynamic), although quoteFeePips() read before swap A was 203000. Any third party's swap in the same block pays 22% instead of 20.3%; after launch, 2.00% instead of 0.30%, decaying over 8 blocks (dynamicFeePips() at age 1 = 3000 + 340*7*50/8 = 17875).","severity":"low","snippet":"                weightedMovement += uint256(o.tickMovement) * (VOLATILITY_BLOCKS - age);","title":"Volatility fee can be pushed to the 2% cap for every trader for 8 blocks by one small round-trip; the first swap in a block also raises the fee for every later swap in that block"},{"citation":"resolved","description":"When ETH is the specified side, the hook pre-charges the fee via beforeSwapReturnDelta and in afterSwap demands that the pool consumed/produced exactly the adjusted amount. If the trader's sqrtPriceLimitX96 is hit first, or liquidity runs out, the pool fills partially and afterSwap reverts the entire swap with NativeSpecifiedNotFilled. Standard v4 behaviour (and the hook's own token-specified paths) is to settle the partial fill. Integrators that pass a real price limit for slippage protection on ETH-specified trades will see hard reverts rather than partial fills. The README documents this as deliberate; it is recorded here because it is a behavioural difference from every other swap mode of the same pool and from unhooked v4 pools, and should be visible to router/aggregator integrators.","line":234,"path":"src/TidalHook.sol","reproduction":"Existing test test/FeeFlows.t.sol test_partialNativeSpecifiedBuyRevertsWithoutCharge: pool seeded with 1000e18 liquidity in [-60000,60000] at tick 0; router.swap{value: 50 ether}(key, SwapParams(true, -50 ether, TickMath.getSqrtPriceAtTick(-10)), 0) -> whole transaction reverts (NativeSpecifiedNotFilled) with zero output, whereas the same trade with amountSpecified = +X (token-specified) or on a pool without this hook returns a partial fill up to tick -10.","severity":"info","snippet":"            if (nativeAmount != expected) revert NativeSpecifiedNotFilled();","title":"Exact-input ETH buys and exact-output ETH sells revert on any partial fill, so a router price limit that stops the swap early fails the whole trade instead of filling what it can"},{"citation":"resolved","description":"afterInitialize compares the full PoolKey id against getPoolKey(), which fixes currency0 = native ETH, currency1 = $token, fee = 12500 and tickSpacing = 60. The LP fee 12500 matches the launch policy tier, but tick spacing is a per-launch manifest field (pool.tickSpacing). If the manifest step writes any value other than 60, PoolManager.initialize from the factory reverts with WrongPool and the launch cannot proceed (the protected test test_initializesFromTheLaunchFactory reads IMD_TICK_SPACING from the manifest). Not a code defect; recorded so the manifest author writes tickSpacing 60 exactly.","line":38,"path":"src/TidalHook.sol","reproduction":"From the factory address: manager.initialize(PoolKey(Currency(0), Currency(token), 12500, 200, hook), 2^96) -> hook.afterInitialize reverts WrongPool (covered by test/AccessAndLaunch.t.sol test_initializationRejectsWrongTokenFeeSpacingAndHook). With tickSpacing 60 the same call succeeds.","severity":"info","snippet":"    int24 public constant TICK_SPACING = 60;","title":"Pool tick spacing is a hard-coded constant (60); a manifest or factory using any other spacing cannot open the pool"}],"hash":"e9ecd67becda2bf4b5cd9840c857206d7f8b24035f14fb6f79c7308d21541724","nodeId":"ff252d30-60b0-4eb4-b2cd-b829ca907a33","outcome":"completed","summary":"Review complete. The findings file is written and the scratch probe directory removed, so the tree is unchanged apart from `.imd-findings.json`.\n\n**Outcome.** No critical, high, or medium defects in the control-flow area. The hook's execution paths are sound: every callback is PoolManager-gated, the unlock callback is reachable only from the hook's own unlocks with a consumed action flag, fee deltas net to zero in all four swap modes, the fresh-manager claim fallback works at the real launch price, and the patched vendored libraries are equivalent to upstream. All 57 existing tests pass, and my own probes (token-only seeding at the 20 ETH market-cap price, a sync-before-swap router, two swaps in one unlock, plain ETH refusal) behaved as the README describes.\n\n**Findings recorded (4):**\n- **Low, buyback stranding.** The buyback half of every fee is spendable only by the owner through `executeBuyback`. There is no permissionless fallback, so a lost or idle owner strands 50% of all fees forever. This deviates from the brief's wording that the half \"is used to buy back\".\n- **Low, volatility fee griefing.** One 340-tick move in a block sets the 2% cap for every trader for 8 blocks, and the first swap in a block raises the fee for later swaps in that block. Observed concretely: the second swap in one unlock paid 22% where the quote before the first swap was 20.3%.\n- **Info, partial-fill reverts.** ETH-specified swaps revert on any partial fill, unlike the token-specified modes and unhooked pools. Documented by the author, recorded for integrators.\n- **Info, hard-coded tick spacing.** The manifest must write tickSpacing 60 exactly or the factory's initialize reverts.\n\n**Coverage.** All 14 listed entry points have rows (12 hold, 2 reference findings), plus rows for the beforeSwap callback, the asset-backing invariant, and the patched periphery libraries. Nothing was left unreached. No proof files were attached since no finding is high or critical.","treeHash":null,"usage":{"cachedInputTokens":1482955,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":52913,"runtime":"claude","turns":34,"wallClockMs":794154}}],"verification":[{"checks":[{"durationMs":7909,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.62s\nCompiler run successful!\n","passed":true},{"durationMs":2611,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/TidalToken.t.sol:TidalTokenTest\n[PASS] testFuzz_transferAndAllowanceConserve(uint128) (runs: 256, μ: 224818, ~: 225798)\nLogs:\n  Bound result 266\n\n[PASS] test_holderCanBurnOnlyOwnBalance() (gas: 167310)\n[PASS] test_launchSupplyAndMetadata() (gas: 52806)\n[PASS] test_noMintOrAdminEntrypoints() (gas: 132054)\n[PASS] test_transferToZeroAndOverspendRefused() (gas: 60925)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 16.23ms (16.35ms CPU time)\n\nRan 8 tests for test/Volatility.t.sol:VolatilityTest\n[PASS] testFuzz_launchDecay(uint64) (runs: 256, μ: 53553, ~: 53496)\n[PASS] test_largeShockCapsFeeAndIdleMarketResets() (gas: 671387)\n[PASS] test_launchSurchargeEveryBlockAndBoundary() (gas: 1219291)\n[PASS] test_multipleBlockObservationsAgeIndependently() (gas: 675136)\n[PASS] test_sameBlockRoundTripsAccumulateAbsoluteMovement() (gas: 783758)\n[PASS] test_swapPaysPriorSignalAndNextSwapPaysNewSignal() (gas: 555306)\n[PASS] test_totalLaunchAndVolatilityFeeNeverExceeds22Percent() (gas: 677454)\n[PASS] test_volatilePriceRaisesFeeThenDecaysEachBlock() (gas: 708031)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 18.31ms (18.59ms CPU time)\n\nRan 11 tests for test/FeeFlows.t.sol:FeeFlowsTest\n[PASS] testFuzz_fourSwapModesConserve(uint96,bool,bool,uint8) (runs: 256, μ: 543824, ~: 546008)\nLogs:\n  Bound result 725707522459565336\n  Bound result 41\n\n[PASS] test_buyExactInputFundedAndFresh() (gas: 15698295)\n[PASS] test_buyExactOutputFundedAndFresh() (gas: 15652225)\n[PASS] test_factoryCanCollectAndWithdrawLiquidity() (gas: 1006140)\n[PASS] test_noLiquidityDoesNotCollectFee() (gas: 621815)\n[PASS] test_partialBuyExactOutputChargesOnlyActualInput() (gas: 498173)\n[PASS] test_partialNativeSpecifiedBuyRevertsWithoutCharge() (gas: 348380)\n[PASS] test_partialNativeSpecifiedSellRevertsWithoutCharge() (gas: 356773)\n[PASS] test_partialSellExactInputChargesOnlyFilledETH() (gas: 529315)\n[PASS] test_sellExactInputFundedAndFresh() (gas: 15874845)\n[PASS] test_sellExactOutputFundedAndFresh() (gas: 15880318)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 77.13ms (152.40ms CPU time)\n\nRan 7 tests for test/MathRegression.t.sol:MathRegressionTest\n[PASS] testFuzz_bitIndicesMatchSlowReference(uint256) (runs: 256, μ: 13851, ~: 11014)\n[PASS] testFuzz_fullPrecisionInverseSeed(uint256,uint128,uint128) (runs: 256, μ: 5096, ~: 5010)\n[PASS] testFuzz_fullWidthDenominatorCancellation(uint256,uint256) (runs: 256, μ: 3531, ~: 3420)\n[PASS] testFuzz_tickMathRoundTrip(int24) (runs: 256, μ: 14128, ~: 14001)\nLogs:\n  Bound result -5\n\n[PASS] test_everySingleBitAndAdjacentValues() (gas: 1613561)\n[PASS] test_fullPrecisionExtremeProductAndFailures() (gas: 13966)\n[PASS] test_zeroBitSearchReverts() (gas: 10707)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 138.65ms (32.49ms CPU time)\n\nRan 13 tests for test/BuybackTreasury.t.sol:BuybackTreasuryTest\n[PASS] testFuzz_splitRemainsExactAcrossClaimsAndBurns(uint96) (runs: 256, μ: 1397128, ~: 1400559)\nLogs:\n  Bound result 1000000000003\n\n[PASS] test_anyoneCanRedeemClaimsToHookOnly() (gas: 27181066)\n[PASS] test_buybackInvalidInputsAndUnauthorizedCaller() (gas: 813950)\n[PASS] test_buybackOutputLimitFailureRollsBackEverything() (gas: 853523)\n[PASS] test_buybackSpendsOnlyAllocationAndReallyBurns() (gas: 772010)\n[PASS] test_claimPaysConfiguredTreasuryNotCaller() (gas: 633866)\n[PASS] test_claimTreasuryAutomaticallyRedeemsFreshClaims() (gas: 27179649)\n[PASS] test_failedRedemptionRollsBackAndRemainsRetryable() (gas: 27215392)\n[PASS] test_freshClaimsCanFundBuybackWithoutRedemption() (gas: 27346666)\n[PASS] test_oddWeiSplitCarriesAcrossSwaps() (gas: 774303)\n[PASS] test_partialBuybackKeepsUnusedBudget() (gas: 694955)\n[PASS] test_rejectingTreasuryDoesNotBlockSwapsOrBurns() (gas: 1239790)\n[PASS] test_treasuryReentrancyCannotDoubleClaim() (gas: 881560)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 139.36ms (324.89ms CPU time)\n\nRan 11 tests for test/AccessAndLaunch.t.sol:AccessAndLaunchTest\n[PASS] test_allImplementedCallbacksRejectNonManager() (gas: 140904)\n[PASS] test_constructorUsesExplicitOwnerAndNoExternalCalls() (gas: 15184697)\n[PASS] test_hookPermissionsExactlyMatchAddressAndCallbacks() (gas: 18509)\n[PASS] test_initializationRejectsWrongTokenFeeSpacingAndHook() (gas: 26766035)\n[PASS] test_invalidConstructorInputsAndUnminedAddressRefused() (gas: 8347)\n[PASS] test_onlyFactoryCanInitializeOnce() (gas: 26560221)\n[PASS] test_ownerControlsTreasuryAndTwoStepOwnership() (gas: 403025)\n[PASS] test_runtimeHasNoEscapeHatches() (gas: 4903196)\n[PASS] test_swapAmountBounds() (gas: 90102)\n[PASS] test_swapCannotUseAnotherPoolOrHookDataExemption() (gas: 125333)\n[PASS] test_uninitializedHookIsClearlyUnavailable() (gas: 26235203)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 139.77ms (189.65ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 11\n  Bound result 999999990000004014\n  Bound result 6\n  Bound result 999999990000002874\n  Bound result 999999990000005154\n  Bound result 3500\n  Bound result 999999992592286114\n  Bound result 999999990000007143\n  Bound result 24\n  Bound result 25\n  Bound result 4\n  Bound result 999999994288775754\n  Bound result 2\n  Bound result 4280\n  Bound result 999999990000000618\n  Bound result 999999990000005617\n  Bound result 999999990000000970\n  Bound result 999999990079376899\n  Bound result 3275\n  Bound result 19\n  Bound result 999999990000019505\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.15s (2.14s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:ClaimsAccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 11\n  Bound result 999999990000000833\n  Bound result 6\n  Bound result 4357391075267662916\n  Bound result 1777\n  Bound result 999999990000010796\n  Bound result 1000000000000000\n  Bound result 999999990000000701\n  Bound result 1000000000000000\n  Bound result 3500\n  Bound result 7090\n  Bound result 999999990000003871\n  Bound result 999999990000003554\n  Bound result 23\n  Bound result 25\n  Bound result 4\n  Bound result 999999990000004832\n  Bound result 2\n  Bound result 8174\n  Bound result 999999990000011719\n  Bound result 174658446912982530\n  Bound result 999999990000002095\n  Bound result 4834\n  Bound result 999999990000012922\n  Bound result 999999990079376899\n  Bound result 2086\n  Bound result 19\n  Bound result 4\n  Bound result 999999990000005372\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.32s (2.31s CPU time)\n\nRan 8 test suites in 2.33s (5.01s CPU time): 57 tests passed, 0 failed, 0 skipped (57 total tests)\n","passed":true},{"durationMs":152,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TidalHook.acceptOwnership()\",\"TidalHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"TidalHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TidalHook.claimTreasury()\",\"TidalHook.executeBuyback(uint256,uint256,uint160,uint256)\",\"TidalHook.receive()\",\"TidalHook.redeemClaims(uint256)\",\"TidalHook.setTreasury(address)\",\"TidalHook.transferOwnership(address)\",\"TidalHook.unlockCallback(bytes)\",\"TidalToken.approve(address,uint256)\",\"TidalToken.burn(uint256)\",\"TidalToken.transfer(address,uint256)\",\"TidalToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":23,\"README.md\":219,\"docs/SECURITY_REVIEW.md\":131,\"foundry.toml\":21,\"launch.json\":32,\"remappings.txt\":4,\"src/TidalHook.sol\":431,\"src/TidalToken.sol\":19,\"test/AccessAndLaunch.t.sol\":208,\"test/AccountingInvariant.t.sol\":151,\"test/BuybackTreasury.t.sol\":243,\"test/FeeFlows.t.sol\":173,\"test/MathRegression.t.sol\":96,\"test/TidalToken.t.sol\":75,\"test/Volatility.t.sol\":102,\"test/helpers/Fixture.sol\":122,\"test/helpers/TestRouter.sol\":108},\"excluded\":[\"deps/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0553d0f1b6da25d2d4a1014c1f18dacbe31fb84acb858797088982c6ff6c33d5","verifiedTreeHash":"60718a76f7de6926fd1daafb8fbea2100ab7ce2a","verifierVersion":"0.1.0+3dcb4df2"},{"checks":[{"durationMs":6223,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.99s\nCompiler run successful!\n","passed":true},{"durationMs":563,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/TidalToken.t.sol:TidalTokenTest\n[PASS] test_metadata() (gas: 29101)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 29968)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 96718)\n[PASS] test_transferFromNeedsAllowance() (gas: 137291)\n[PASS] test_transferMovesExactly() (gas: 74823)\n[PASS] test_transferOverBalanceReverts() (gas: 32895)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.61ms (1.89ms CPU time)\n\nRan 5 tests for test/TidalHookFuzz.t.sol:TidalHookFuzzTest\n[PASS] testFuzz_exactInputBuy(uint96,uint8) (runs: 256, μ: 472124, ~: 474982)\n[PASS] testFuzz_exactInputSell(uint96,uint8) (runs: 256, μ: 475998, ~: 479079)\n[PASS] testFuzz_feeForNeverExceedsTheAmount(uint128,uint24) (runs: 256, μ: 13463, ~: 13745)\n[PASS] testFuzz_feeIsBounded(uint256) (runs: 256, μ: 51025, ~: 50895)\n[PASS] testFuzz_sequenceKeepsAccounting(uint256,uint8) (runs: 256, μ: 1797749, ~: 1536485)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 273.47ms (461.67ms CPU time)\n\nRan 39 tests for test/TidalHook.t.sol:TidalHookTest\n[PASS] test_acceptsOtherStaticFeeTiers() (gas: 197225)\n[PASS] test_addressCarriesExactlyTheDeclaredPermissions() (gas: 11154)\n[PASS] test_buyOnATokenOnlyPoolMintsClaimsAndStillBurns() (gas: 1066113)\n[PASS] test_buybackReserveWhenLiquidityRunsOut() (gas: 1299391)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 151246)\n[PASS] test_claimTreasuryPaysTheTreasury() (gas: 970652)\n[PASS] test_claimTreasuryWithNothingOwedReverts() (gas: 36575)\n[PASS] test_constructorArguments() (gas: 38039)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 6119)\n[PASS] test_deployingAtAnAddressWithoutTheFlagsReverts() (gas: 8196)\n[PASS] test_exactInputBuyTakesTheFeeFromTheEthIn() (gas: 982494)\n[PASS] test_exactInputSellTakesTheFeeFromTheEthOut() (gas: 895155)\n[PASS] test_exactOutputBuyAddsTheFeeOnTopOfTheEthIn() (gas: 918474)\n[PASS] test_exactOutputSellDeliversExactlyTheEthAsked() (gas: 925849)\n[PASS] test_executeBuybackSpendsTheReserveOnceThereIsLiquidity() (gas: 2434885)\n[PASS] test_factoryCollectsLpFeesWithAZeroLiquidityChange() (gas: 1159552)\n[PASS] test_factoryOpensThePool() (gas: 258314)\n[PASS] test_feeForMath() (gas: 18065)\n[PASS] test_launchFeeIsChargedOnTheOpeningBlock() (gas: 842537)\n[PASS] test_launchFeeStartsAtTwentyPercentAndDecaysLinearly() (gas: 355216)\n[PASS] test_liquidityCanBeRemoved() (gas: 894619)\n[PASS] test_manySwapsKeepTheAccountingBalanced() (gas: 12218135)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 51668)\n[PASS] test_ownerIsNeitherTheFactoryNorTheDeployTxOrigin() (gas: 25084)\n[PASS] test_redeemClaimsWaitsForEthInTheManager() (gas: 918520)\n[PASS] test_refusesADynamicFeePool() (gas: 49938)\n[PASS] test_refusesATokenPair() (gas: 534282)\n[PASS] test_sellDuringLaunchPaysTheLaunchFee() (gas: 877968)\n[PASS] test_setTreasuryIsOwnerOnly() (gas: 121470)\n[PASS] test_strayEthIsRefused() (gas: 35938)\n[PASS] test_swapBeforeInitializationIsRefused() (gas: 44368)\n[PASS] test_swapCallbacksRefuseAnotherPool() (gas: 263422)\n[PASS] test_thePoolOpensOnlyOnce() (gas: 230935)\n[PASS] test_tinySwapsDoNotRevert() (gas: 967319)\n[PASS] test_treasuryThatRejectsEthDoesNotBlockSwaps() (gas: 1382293)\n[PASS] test_unimplementedCallbacksRevertEvenForTheManager() (gas: 36855)\n[PASS] test_volatilityFeeIsCappedAtTwoPercent() (gas: 1087991)\n[PASS] test_volatilityRaisesTheFeeAndItDecays() (gas: 1190902)\n[PASS] test_volatilityWindowForgetsOldBlocks() (gas: 841718)\nSuite result: ok. 39 passed; 0 failed; 0 skipped; finished in 274.05ms (137.19ms CPU time)\n\nRan 3 test suites in 275.07ms (549.13ms CPU time): 50 tests passed, 0 failed, 0 skipped (50 total tests)\n","passed":true},{"durationMs":151,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TidalHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TidalHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TidalHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TidalHook.claimTreasury()\",\"TidalHook.executeBuyback()\",\"TidalHook.flushBurn()\",\"TidalHook.receive()\",\"TidalHook.redeemClaims()\",\"TidalHook.renounceOwnership()\",\"TidalHook.setTreasury(address)\",\"TidalHook.transferOwnership(address)\",\"TidalHook.unlockCallback(bytes)\",\"TidalToken.approve(address,uint256)\",\"TidalToken.transfer(address,uint256)\",\"TidalToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"README.md\":191,\"foundry.toml\":22,\"remappings.txt\":4,\"src/HookFlags.sol\":36,\"src/TidalHook.sol\":647,\"src/TidalToken.sol\":18,\"test/TidalHook.t.sol\":611,\"test/TidalHookFuzz.t.sol\":90,\"test/TidalToken.t.sol\":64,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":55,\"test/mocks/Swapper.sol\":59,\"test/utils/HookMiner.sol\":24,\"test/utils/LaunchFactoryMock.sol\":75,\"test/utils/TidalFixture.sol\":163},\"excluded\":[\"deps/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3785,"exitCode":0,"name":"slither","output":"[high/medium] incorrect-exp at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) has bitwise-xor operator ^ instead of the exponentiation operator **: \n[high/high] incorrect-shift at deps/v4-core/src/libraries/BitMath.sol:12: BitMath.mostSignificantBit(uint256) (deps/v4-core/src/libraries/BitMath.sol#12-25) contains an incorrect shift operation: r = r | byte(uint256,uint256)(0x1f & 0x8421084210842108cc6318c6db6d54be >> x >> r,0x0706060506020500060203020504000106050205030304010505030400000000) (deps/v4-core/src/libraries/BitMath.sol#22-23)\n[high/high] incorrect-shift at deps/v4-core/src/libraries/BitMath.sol:31: BitMath.leastSignificantBit(uint256) (deps/v4-core/src/libraries/BitMath.sol#31-48) contains an incorrect shift operation: r = 0x8040405543005266443200005020610674053026020000107506200176117077 << x * 0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff >> 250 << 2 >> 252 << 5 (deps/v4-core/src/libraries/BitMath.sol#40-42)\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:46: TickMath.minUsableTick(int24) (deps/v4-core/src/libraries/TickMath.sol#46-50) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:39: TickMath.maxUsableTick(int24) (deps/v4-core/src/libraries/TickMath.sol#39-43) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:57: TickMath.getSqrtPriceAtTick(int24) (deps/v4-core/src/libraries/TickMath.sol#57-114) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-102) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/CustomRevert.sol:83: CustomRevert.bubbleUpAndRevertWith(address,bytes4,bytes4) (deps/v4-core/src/libraries/CustomRevert.sol#83-119) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/TidalHook.sol:529: TidalHook._deviation(int24) (src/TidalHook.sol#529-540) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/TidalHook.sol:637: TidalHook._redeemClaims(uint256) (src/TidalHook.sol#637-646) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/TidalHook.sol:529: TidalHook._deviation(int24) (src/TidalHook.sol#529-540) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/TidalHook.sol:279: TidalHook.claimTreasury() (src/TidalHook.sol#279-294) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/TidalHook.sol:279: Reentrancy in TidalHook.claimTreasury() (src/TidalHook.sol#279-294):\n[medium/medium] uninitialized-local at deps/v4-core/src/libraries/Hooks.sol:309: Hooks.afterSwap(IHooks,PoolKey,SwapParams,BalanceDelta,bytes,BeforeSwapDelta).hookDelta (deps/v4-core/src/libraries/Hooks.sol#309) is a local variable never initialized\n[medium/medium] unused-return at src/TidalHook.sol:544: TidalHook._updateFee() (src/TidalHook.sol#544-559) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/TidalHook.sol#545)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:322: Hooks.beforeDonate(IHooks,PoolKey,uint256,uint256,bytes) (deps/v4-core/src/libraries/Hooks.sol#322-329) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeDonate,(msg.sender,key,amount0,amount1,hookData))) (deps/v4-core/src/libraries/Hooks.sol#327)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:332: Hooks.afterDonate(IHooks,PoolKey,uint256,uint256,bytes) (deps/v4-core/src/libraries/Hooks.sol#332-339) ignores return value by self.callHook(abi.encodeCall(IHooks.afterDonate,(msg.sender,key,amount0,amount1,hookData))) (deps/v4-core/src/libraries/Hooks.sol#337)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:179: Hooks.beforeInitialize(IHooks,PoolKey,uint160) (deps/v4-core/src/libraries/Hooks.sol#179-183) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeInitialize,(msg.sender,key,sqrtPriceX96))) (deps/v4-core/src/libraries/Hooks.sol#181)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:196: Hooks.beforeModifyLiquidity(IHooks,PoolKey,ModifyLiquidityParams,bytes) (deps/v4-core/src/libraries/Hooks.sol#196-207) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeRemoveLiquidity,(msg.sender,key,params,hookData))) (deps/v4-core/src/libraries/Hooks.sol#205)\n[medium/medium] unused-return at src/TidalHook.sol:238: TidalHook.quoteFeePips() (src/TidalHook.sol#238-243) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/TidalHook.sol#240)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:196: Hooks.beforeModifyLiquidity(IHooks,PoolKey,ModifyLiquidityParams,bytes) (deps/v4-core/src/libraries/Hooks.sol#196-207) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeAddLiquidity,(msg.sender,key,params,hookData))) (deps/v4-core/src/libraries/Hooks.sol#203)\n[medium/medium] unused-return at src/TidalHook.sol:628: TidalHook._payEth(uint256) (src/TidalHook.sol#628-634) ignores return value by poolManager.settle{value: rest}() (src/TidalHook.sol#633)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:186: Hooks.afterInitialize(IHooks,PoolKey,uint160,int24) (deps/v4-core/src/libraries/Hooks.sol#186-193) ignores return value by self.callHook(abi.encodeCall(IHooks.afterInitialize,(msg.sender,key,sqrtPriceX96,tick))) (deps/v4-core/src/libraries/Hooks.sol#191)\n[medium/medium] unused-return at src/TidalHook.sol:302: TidalHook.executeBuyback() (src/TidalHook.sol#302-306) ignores return value by poolManager.unlock(abi.encode(ACTION_BUYBACK,amount)) (src/TidalHook.sol#305)\n[medium/medium] unused-return at src/TidalHook.sol:637: TidalHook._redeemClaims(uint256) (src/TidalHook.sol#637-646) ignores return value by poolManager.unlock(abi.encode(ACTION_REDEEM,amount)) (src/TidalHook.sol#644)\n[medium/medium] unused-return at src/TidalHook.sol:600: TidalHook._buybackSwap(uint256) (src/TidalHook.sol#600-625) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolId) (src/TidalHook.sol#601)\n[medium/medium] unused-return at src/TidalHook.sol:309: TidalHook.flushBurn() (src/TidalHook.sol#309-316) ignores return value by poolManager.unlock(abi.encode(ACTION_FLUSH_BURN,amount)) (src/TidalHook.sol#315)\n[low/medium] missing-zero-check at src/TidalHook.sol:170: TidalHook.constructor(IPoolManager,address,address).owner_ (src/TidalHook.sol#170) lacks a zero-check on :\n[low/high] incorrect-modifier at deps/v4-core/src/libraries/Hooks.sol:172: Modifier Hooks.noSelfCall(IHooks) (deps/v4-core/src/libraries/Hooks.sol#172-176) does not always execute _; or revert\n[low/medium] reentrancy-benign at src/TidalHook.sol:567: Reentrancy in TidalHook._distribute(address,bool,uint24,uint256) (src/TidalHook.sol#567-585):\n[low/medium] reentrancy-benign at src/TidalHook.sol:600: Reentrancy in TidalHook._buybackSwap(uint256) (src/TidalHook.sol#600-625):\n[low/medium] reentrancy-benign at src/TidalHook.sol:319: Reentrancy in TidalHook.unlockCallback(bytes) (src/TidalHook.sol#319-340):\n[low/medium] reentrancy-events at src/TidalHook.sol:567: Reentrancy in TidalHook._distribute(address,bool,uint24,uint256) (src/TidalHook.sol#567-585):\n[low/medium] reentrancy-events at src/TidalHook.sol:567: Reentrancy in TidalHook._distribute(address,bool,uint24,uint256) (src/TidalHook.sol#567-585):\n[low/medium] reentrancy-events at src/TidalHook.sol:319: Reentrancy in TidalHook.unlockCallback(bytes) (src/TidalHook.sol#319-340):\n[low/medium] reentrancy-events at src/TidalHook.sol:600: Reentrancy in TidalHook._buybackSwap(uint256) (src/TidalHook.sol#600-625):","passed":false}],"detail":"static analysis found incorrect-shift at deps/v4-core/src/libraries/BitMath.sol:12; incorrect-shift at deps/v4-core/src/libraries/BitMath.sol:31: BitMath.mostSignificantBit(uint256) (deps/v4-core/src/libraries/BitMath.sol#12-25) contains an incorrect shift operation: r = r | byte(uint256,uint256)(0x1f & 0x8421084210842108cc6318c6db6d54be >> x >> r,0x0706060506020500060203020504000106050205030304010505030400000000) (deps/v4-core/src/libraries/BitMath.sol#22-23)","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"46c1f356eef21bb43fd257cd5cf3a5bda182caa80773a9e4d3722336c8340a75","verifiedTreeHash":"3224a259cb4f684f9f00663d951e8cf0bfe19502","verifierVersion":"0.1.0+3dcb4df2"},{"checks":[{"durationMs":7331,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.13s\nCompiler run successful!\n","passed":true},{"durationMs":2772,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/TidalToken.t.sol:TidalTokenTest\n[PASS] testFuzz_transferAndAllowanceConserve(uint128) (runs: 256, μ: 224818, ~: 225798)\nLogs:\n  Bound result 782445351628489330834204787\n\n[PASS] test_holderCanBurnOnlyOwnBalance() (gas: 167310)\n[PASS] test_launchSupplyAndMetadata() (gas: 52806)\n[PASS] test_noMintOrAdminEntrypoints() (gas: 132054)\n[PASS] test_transferToZeroAndOverspendRefused() (gas: 60925)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 30.97ms (30.83ms CPU time)\n\nRan 8 tests for test/Volatility.t.sol:VolatilityTest\n[PASS] testFuzz_launchDecay(uint64) (runs: 256, μ: 53553, ~: 53496)\n[PASS] test_largeShockCapsFeeAndIdleMarketResets() (gas: 671387)\n[PASS] test_launchSurchargeEveryBlockAndBoundary() (gas: 1219291)\n[PASS] test_multipleBlockObservationsAgeIndependently() (gas: 675136)\n[PASS] test_sameBlockRoundTripsAccumulateAbsoluteMovement() (gas: 783758)\n[PASS] test_swapPaysPriorSignalAndNextSwapPaysNewSignal() (gas: 555306)\n[PASS] test_totalLaunchAndVolatilityFeeNeverExceeds22Percent() (gas: 677454)\n[PASS] test_volatilePriceRaisesFeeThenDecaysEachBlock() (gas: 708031)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 38.76ms (35.17ms CPU time)\n\nRan 7 tests for test/MathRegression.t.sol:MathRegressionTest\n[PASS] testFuzz_bitIndicesMatchSlowReference(uint256) (runs: 256, μ: 13851, ~: 11014)\n[PASS] testFuzz_fullPrecisionInverseSeed(uint256,uint128,uint128) (runs: 256, μ: 5096, ~: 5010)\n[PASS] testFuzz_fullWidthDenominatorCancellation(uint256,uint256) (runs: 256, μ: 3531, ~: 3420)\n[PASS] testFuzz_tickMathRoundTrip(int24) (runs: 256, μ: 14128, ~: 14001)\nLogs:\n  Bound result -5\n\n[PASS] test_everySingleBitAndAdjacentValues() (gas: 1613561)\n[PASS] test_fullPrecisionExtremeProductAndFailures() (gas: 13966)\n[PASS] test_zeroBitSearchReverts() (gas: 10707)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 77.30ms (98.49ms CPU time)\n\nRan 11 tests for test/AccessAndLaunch.t.sol:AccessAndLaunchTest\n[PASS] test_allImplementedCallbacksRejectNonManager() (gas: 140904)\n[PASS] test_constructorUsesExplicitOwnerAndNoExternalCalls() (gas: 15184697)\n[PASS] test_hookPermissionsExactlyMatchAddressAndCallbacks() (gas: 18509)\n[PASS] test_initializationRejectsWrongTokenFeeSpacingAndHook() (gas: 26766035)\n[PASS] test_invalidConstructorInputsAndUnminedAddressRefused() (gas: 8347)\n[PASS] test_onlyFactoryCanInitializeOnce() (gas: 26560221)\n[PASS] test_ownerControlsTreasuryAndTwoStepOwnership() (gas: 403025)\n[PASS] test_runtimeHasNoEscapeHatches() (gas: 4903196)\n[PASS] test_swapAmountBounds() (gas: 90102)\n[PASS] test_swapCannotUseAnotherPoolOrHookDataExemption() (gas: 125333)\n[PASS] test_uninitializedHookIsClearlyUnavailable() (gas: 26235203)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 77.47ms (222.09ms CPU time)\n\nRan 11 tests for test/FeeFlows.t.sol:FeeFlowsTest\n[PASS] testFuzz_fourSwapModesConserve(uint96,bool,bool,uint8) (runs: 256, μ: 543824, ~: 546008)\nLogs:\n  Bound result 725707522459565338\n  Bound result 39\n\n[PASS] test_buyExactInputFundedAndFresh() (gas: 15698295)\n[PASS] test_buyExactOutputFundedAndFresh() (gas: 15652225)\n[PASS] test_factoryCanCollectAndWithdrawLiquidity() (gas: 1006140)\n[PASS] test_noLiquidityDoesNotCollectFee() (gas: 621815)\n[PASS] test_partialBuyExactOutputChargesOnlyActualInput() (gas: 498173)\n[PASS] test_partialNativeSpecifiedBuyRevertsWithoutCharge() (gas: 348380)\n[PASS] test_partialNativeSpecifiedSellRevertsWithoutCharge() (gas: 356773)\n[PASS] test_partialSellExactInputChargesOnlyFilledETH() (gas: 529315)\n[PASS] test_sellExactInputFundedAndFresh() (gas: 15874845)\n[PASS] test_sellExactOutputFundedAndFresh() (gas: 15880318)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 77.54ms (190.47ms CPU time)\n\nRan 13 tests for test/BuybackTreasury.t.sol:BuybackTreasuryTest\n[PASS] testFuzz_splitRemainsExactAcrossClaimsAndBurns(uint96) (runs: 256, μ: 1397128, ~: 1400559)\nLogs:\n  Bound result 1000000000003\n\n[PASS] test_anyoneCanRedeemClaimsToHookOnly() (gas: 27181066)\n[PASS] test_buybackInvalidInputsAndUnauthorizedCaller() (gas: 813950)\n[PASS] test_buybackOutputLimitFailureRollsBackEverything() (gas: 853523)\n[PASS] test_buybackSpendsOnlyAllocationAndReallyBurns() (gas: 772010)\n[PASS] test_claimPaysConfiguredTreasuryNotCaller() (gas: 633866)\n[PASS] test_claimTreasuryAutomaticallyRedeemsFreshClaims() (gas: 27179649)\n[PASS] test_failedRedemptionRollsBackAndRemainsRetryable() (gas: 27215392)\n[PASS] test_freshClaimsCanFundBuybackWithoutRedemption() (gas: 27346666)\n[PASS] test_oddWeiSplitCarriesAcrossSwaps() (gas: 774303)\n[PASS] test_partialBuybackKeepsUnusedBudget() (gas: 694955)\n[PASS] test_rejectingTreasuryDoesNotBlockSwapsOrBurns() (gas: 1239790)\n[PASS] test_treasuryReentrancyCannotDoubleClaim() (gas: 881560)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 160.83ms (378.52ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 11\n  Bound result 999999990000004014\n  Bound result 6\n  Bound result 999999990000002874\n  Bound result 999999990000005154\n  Bound result 3500\n  Bound result 999999992592286114\n  Bound result 999999990000007143\n  Bound result 24\n  Bound result 25\n  Bound result 4\n  Bound result 999999994288775754\n  Bound result 2\n  Bound result 4280\n  Bound result 999999990000000618\n  Bound result 999999990000005617\n  Bound result 999999990000000970\n  Bound result 999999990079376899\n  Bound result 3275\n  Bound result 19\n  Bound result 999999990000019505\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.18s (2.16s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:ClaimsAccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 11\n  Bound result 999999990000000833\n  Bound result 6\n  Bound result 4357391075267662916\n  Bound result 1777\n  Bound result 999999990000010796\n  Bound result 1000000000000000\n  Bound result 999999990000000701\n  Bound result 1000000000000000\n  Bound result 3500\n  Bound result 7090\n  Bound result 999999990000003871\n  Bound result 999999990000003554\n  Bound result 23\n  Bound result 25\n  Bound result 4\n  Bound result 999999990000004832\n  Bound result 2\n  Bound result 8174\n  Bound result 999999990000011719\n  Bound result 174658446912982530\n  Bound result 999999990000002095\n  Bound result 4834\n  Bound result 999999990000012922\n  Bound result 999999990079376899\n  Bound result 2086\n  Bound result 19\n  Bound result 4\n  Bound result 999999990000005372\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.45s (2.44s CPU time)\n\nRan 8 test suites in 2.46s (5.10s CPU time): 57 tests passed, 0 failed, 0 skipped (57 total tests)\n","passed":true},{"durationMs":179,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TidalHook.acceptOwnership()\",\"TidalHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"TidalHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TidalHook.claimTreasury()\",\"TidalHook.executeBuyback(uint256,uint256,uint160,uint256)\",\"TidalHook.receive()\",\"TidalHook.redeemClaims(uint256)\",\"TidalHook.setTreasury(address)\",\"TidalHook.transferOwnership(address)\",\"TidalHook.unlockCallback(bytes)\",\"TidalToken.approve(address,uint256)\",\"TidalToken.burn(uint256)\",\"TidalToken.transfer(address,uint256)\",\"TidalToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":23,\"README.md\":219,\"docs/SECURITY_REVIEW.md\":131,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TidalHook.sol\":431,\"src/TidalToken.sol\":19,\"test/AccessAndLaunch.t.sol\":208,\"test/AccountingInvariant.t.sol\":151,\"test/BuybackTreasury.t.sol\":243,\"test/FeeFlows.t.sol\":173,\"test/MathRegression.t.sol\":96,\"test/TidalToken.t.sol\":75,\"test/Volatility.t.sol\":102,\"test/helpers/Fixture.sol\":122,\"test/helpers/TestRouter.sol\":108},\"excluded\":[\"deps/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":4538,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:46: TickMath.minUsableTick(int24) (deps/v4-core/src/libraries/TickMath.sol#46-50) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:39: TickMath.maxUsableTick(int24) (deps/v4-core/src/libraries/TickMath.sol#39-43) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/TickMath.sol:57: TickMath.getSqrtPriceAtTick(int24) (deps/v4-core/src/libraries/TickMath.sol#57-117) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/FullMath.sol:14: FullMath.mulDiv(uint256,uint256,uint256) (deps/v4-core/src/libraries/FullMath.sol#14-106) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at deps/v4-core/src/libraries/CustomRevert.sol:83: CustomRevert.bubbleUpAndRevertWith(address,bytes4,bytes4) (deps/v4-core/src/libraries/CustomRevert.sol#83-122) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/TidalHook.sol:383: TidalHook._redeem(uint256) (src/TidalHook.sol#383-390) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/TidalHook.sol:347: TidalHook._collectFee(uint256,uint24) (src/TidalHook.sol#347-363) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/TidalHook.sol:208: Reentrancy in TidalHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/TidalHook.sol#208-245):\n[medium/medium] reentrancy-no-eth at src/TidalHook.sol:281: Reentrancy in TidalHook.executeBuyback(uint256,uint256,uint160,uint256) (src/TidalHook.sol#281-310):\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:190: Hooks.beforeInitialize(IHooks,PoolKey,uint160) (deps/v4-core/src/libraries/Hooks.sol#190-197) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeInitialize,(msg.sender,key,sqrtPriceX96))) (deps/v4-core/src/libraries/Hooks.sol#195)\n[medium/medium] unused-return at src/TidalHook.sol:281: TidalHook.executeBuyback(uint256,uint256,uint160,uint256) (src/TidalHook.sol#281-310) ignores return value by (currentPrice,None,None,None) = poolManager.getSlot0(poolId) (src/TidalHook.sol#297)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:342: Hooks.beforeDonate(IHooks,PoolKey,uint256,uint256,bytes) (deps/v4-core/src/libraries/Hooks.sol#342-352) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeDonate,(msg.sender,key,amount0,amount1,hookData))) (deps/v4-core/src/libraries/Hooks.sol#350)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:210: Hooks.beforeModifyLiquidity(IHooks,PoolKey,ModifyLiquidityParams,bytes) (deps/v4-core/src/libraries/Hooks.sol#210-221) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeRemoveLiquidity,(msg.sender,key,params,hookData))) (deps/v4-core/src/libraries/Hooks.sol#219)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:355: Hooks.afterDonate(IHooks,PoolKey,uint256,uint256,bytes) (deps/v4-core/src/libraries/Hooks.sol#355-365) ignores return value by self.callHook(abi.encodeCall(IHooks.afterDonate,(msg.sender,key,amount0,amount1,hookData))) (deps/v4-core/src/libraries/Hooks.sol#363)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:210: Hooks.beforeModifyLiquidity(IHooks,PoolKey,ModifyLiquidityParams,bytes) (deps/v4-core/src/libraries/Hooks.sol#210-221) ignores return value by self.callHook(abi.encodeCall(IHooks.beforeAddLiquidity,(msg.sender,key,params,hookData))) (deps/v4-core/src/libraries/Hooks.sol#217)\n[medium/medium] unused-return at src/TidalHook.sol:365: TidalHook._observe() (src/TidalHook.sol#365-381) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/TidalHook.sol#366)\n[medium/medium] unused-return at deps/v4-core/src/libraries/Hooks.sol:200: Hooks.afterInitialize(IHooks,PoolKey,uint160,int24) (deps/v4-core/src/libraries/Hooks.sol#200-207) ignores return value by self.callHook(abi.encodeCall(IHooks.afterInitialize,(msg.sender,key,sqrtPriceX96,tick))) (deps/v4-core/src/libraries/Hooks.sol#205)\n[low/medium] missing-zero-check at src/TidalHook.sol:104: TidalHook.constructor(IPoolManager,TidalToken,address,address).initialOwner (src/TidalHook.sol#104) lacks a zero-check on :\n[low/medium] missing-zero-check at deps/openzeppelin-contracts/contracts/access/Ownable2Step.sol:43: Ownable2Step.transferOwnership(address).newOwner (deps/openzeppelin-contracts/contracts/access/Ownable2Step.sol#43) lacks a zero-check on :\n[low/high] incorrect-modifier at deps/v4-core/src/libraries/Hooks.sol:183: Modifier Hooks.noSelfCall(IHooks) (deps/v4-core/src/libraries/Hooks.sol#183-187) does not always execute _; or revert\n[low/medium] reentrancy-benign at src/TidalHook.sol:312: Reentrancy in TidalHook.unlockCallback(bytes) (src/TidalHook.sol#312-338):\n[low/medium] reentrancy-benign at src/TidalHook.sol:208: Reentrancy in TidalHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/TidalHook.sol#208-245):\n[low/medium] reentrancy-benign at src/TidalHook.sol:312: Reentrancy in TidalHook.unlockCallback(bytes) (src/TidalHook.sol#312-338):\n[low/medium] reentrancy-benign at src/TidalHook.sol:383: Reentrancy in TidalHook._redeem(uint256) (src/TidalHook.sol#383-390):\n[low/medium] reentrancy-benign at src/TidalHook.sol:281: Reentrancy in TidalHook.executeBuyback(uint256,uint256,uint160,uint256) (src/TidalHook.sol#281-310):\n[low/medium] reentrancy-events at src/TidalHook.sol:312: Reentrancy in TidalHook.unlockCallback(bytes) (src/TidalHook.sol#312-338):\n[low/medium] reentrancy-events at src/TidalHook.sol:208: Reentrancy in TidalHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/TidalHook.sol#208-245):\n[low/medium] reentrancy-events at src/TidalHook.sol:347: Reentrancy in TidalHook._collectFee(uint256,uint24) (src/TidalHook.sol#347-363):\n[low/medium] timestamp at src/TidalHook.sol:281: TidalHook.executeBuyback(uint256,uint256,uint160,uint256) (src/TidalHook.sol#281-310) uses timestamp for comparisons","passed":true},{"durationMs":1126,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/TidalHook.sol:261: ETH transferred without address checks\n[high] reentrancy-state-change at src/TidalHook.sol:303: Reentrancy: State change after external call (2 places)\n[high] weak-randomness at src/TidalHook.sol:370: Weak Randomness\n[low] centralization-risk at src/TidalHook.sol:25: Centralization Risk (4 places)\n[low] large-numeric-literal at src/TidalHook.sol:29: Large Numeric Literal (4 places)\n[low] state-no-address-check at src/TidalHook.sol:113: Address State Variable Set Without Checks\n[low] uninitialized-local-variable at src/TidalHook.sol:170: Uninitialized Local Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5120c3c9ee81ecd2f2349b520be0c6280e6a5c1150e0cb1fe6c788046b8bc407","verifiedTreeHash":"d3ec1d7bbc70c31123f2ee57a290dc210db52393","verifierVersion":"0.1.0+3dcb4df2"},{"checks":[{"durationMs":14439,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 14.16s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/LifecycleInvariant.t.sol:22:9:\n   |\n22 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\n","passed":true},{"durationMs":13385,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/Volatility.t.sol:VolatilityTest\n[PASS] testFuzz_launchDecay(uint64) (runs: 256, μ: 53550, ~: 53496)\n[PASS] test_largeShockCapsFeeAndIdleMarketResets() (gas: 671387)\n[PASS] test_launchSurchargeEveryBlockAndBoundary() (gas: 1219291)\n[PASS] test_multipleBlockObservationsAgeIndependently() (gas: 675136)\n[PASS] test_sameBlockRoundTripsAccumulateAbsoluteMovement() (gas: 783758)\n[PASS] test_swapPaysPriorSignalAndNextSwapPaysNewSignal() (gas: 555306)\n[PASS] test_totalLaunchAndVolatilityFeeNeverExceeds22Percent() (gas: 677454)\n[PASS] test_volatilePriceRaisesFeeThenDecaysEachBlock() (gas: 708031)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 57.00ms (34.09ms CPU time)\n\nRan 5 tests for test/TidalToken.t.sol:TidalTokenTest\n[PASS] testFuzz_transferAndAllowanceConserve(uint128) (runs: 256, μ: 225045, ~: 225798)\nLogs:\n  Bound result 3\n\n[PASS] test_holderCanBurnOnlyOwnBalance() (gas: 167310)\n[PASS] test_launchSupplyAndMetadata() (gas: 52806)\n[PASS] test_noMintOrAdminEntrypoints() (gas: 132054)\n[PASS] test_transferToZeroAndOverspendRefused() (gas: 60925)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 116.07ms (114.82ms CPU time)\n\nRan 2 tests for test/UnequalPriceFlows.t.sol:UnequalPriceFlowsTest\n[PASS] test_allSwapModesAndBuybackAtUnequalPriceWithCashFees() (gas: 28352946)\n[PASS] test_allSwapModesAndBuybackAtUnequalPriceWithFreshClaims() (gas: 28435559)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 141.08ms (194.96ms CPU time)\n\nRan 4 tests for test/AtomicSettlement.t.sol:AtomicSettlementTest\n[PASS] testFuzz_fourModesNetSettleInsideOneUnlock(uint96,uint8) (runs: 1000, μ: 3738126, ~: 3746672)\nLogs:\n  Bound result 0\n  Bound result 2367473957\n\n[PASS] test_buybackClearsPreexistingTokenSyncBeforePayingETH() (gas: 3678692)\n[PASS] test_nestedUnlockFailuresLeaveAuthorizationRetryable() (gas: 30706597)\n[PASS] test_twoUnsettledBuysMintClaimsOnFreshManager() (gas: 30271556)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 497.89ms (603.75ms CPU time)\n\nRan 13 tests for test/BuybackTreasury.t.sol:BuybackTreasuryTest\n[PASS] testFuzz_splitRemainsExactAcrossClaimsAndBurns(uint96) (runs: 256, μ: 1397128, ~: 1400559)\nLogs:\n  Bound result 6288611074068405402\n\n[PASS] test_anyoneCanRedeemClaimsToHookOnly() (gas: 27181066)\n[PASS] test_buybackInvalidInputsAndUnauthorizedCaller() (gas: 813950)\n[PASS] test_buybackOutputLimitFailureRollsBackEverything() (gas: 853523)\n[PASS] test_buybackSpendsOnlyAllocationAndReallyBurns() (gas: 772010)\n[PASS] test_claimPaysConfiguredTreasuryNotCaller() (gas: 633866)\n[PASS] test_claimTreasuryAutomaticallyRedeemsFreshClaims() (gas: 27179649)\n[PASS] test_failedRedemptionRollsBackAndRemainsRetryable() (gas: 27215392)\n[PASS] test_freshClaimsCanFundBuybackWithoutRedemption() (gas: 27346666)\n[PASS] test_oddWeiSplitCarriesAcrossSwaps() (gas: 774303)\n[PASS] test_partialBuybackKeepsUnusedBudget() (gas: 694955)\n[PASS] test_rejectingTreasuryDoesNotBlockSwapsOrBurns() (gas: 1239790)\n[PASS] test_treasuryReentrancyCannotDoubleClaim() (gas: 881560)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 526.91ms (881.39ms CPU time)\n\nRan 11 tests for test/FailureAtomicity.t.sol:FailureAtomicityTest\n[PASS] test_buybackSlippageRollsBackWithCashAndClaims() (gas: 28040436)\n[PASS] test_buybackSlippageRollsBackWithClaimsOnly() (gas: 27946218)\n[PASS] test_buybackWithoutLiquidityPreservesBudgetAndCanRetry() (gas: 1894199)\n[PASS] test_cancelledAndReplacedNomineesCannotTakeControlOfAccruedFunds() (gas: 1357271)\n[PASS] test_elapsedSecondsCannotExpireLaunchOrVolatilityBlocks() (gas: 434704)\n[PASS] test_nativeReceiveRejectsNonManagerWithoutAcceptingValue() (gas: 409588)\n[PASS] test_nativeSpecifiedPartialFillsHavePreciseErrorAndRollback() (gas: 1465266)\n[PASS] test_ownerTreasuryCannotReenterAnyGuardedFundOperation() (gas: 1158977)\n[PASS] test_rejectedPayoutRestoresRedeemedClaimsAndObservationHistory() (gas: 27965998)\n[PASS] test_underfundedRouterRollsBackClaimMintAndCanRetry() (gas: 27961636)\n[PASS] test_underfundedRouterRollsBackDirectFeeAndCanRetry() (gas: 1221708)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 604.12ms (350.16ms CPU time)\n\nRan 7 tests for test/MathRegression.t.sol:MathRegressionTest\n[PASS] testFuzz_bitIndicesMatchSlowReference(uint256) (runs: 256, μ: 14481, ~: 12138)\n[PASS] testFuzz_fullPrecisionInverseSeed(uint256,uint128,uint128) (runs: 256, μ: 5102, ~: 5010)\n[PASS] testFuzz_fullWidthDenominatorCancellation(uint256,uint256) (runs: 256, μ: 3531, ~: 3420)\n[PASS] testFuzz_tickMathRoundTrip(int24) (runs: 256, μ: 14152, ~: 14020)\nLogs:\n  Bound result 177300\n\n[PASS] test_everySingleBitAndAdjacentValues() (gas: 1613561)\n[PASS] test_fullPrecisionExtremeProductAndFailures() (gas: 13966)\n[PASS] test_zeroBitSearchReverts() (gas: 10707)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 611.15ms (707.93ms CPU time)\n\nRan 11 tests for test/AccessAndLaunch.t.sol:AccessAndLaunchTest\n[PASS] test_allImplementedCallbacksRejectNonManager() (gas: 140904)\n[PASS] test_constructorUsesExplicitOwnerAndNoExternalCalls() (gas: 15184697)\n[PASS] test_hookPermissionsExactlyMatchAddressAndCallbacks() (gas: 18509)\n[PASS] test_initializationRejectsWrongTokenFeeSpacingAndHook() (gas: 26766035)\n[PASS] test_invalidConstructorInputsAndUnminedAddressRefused() (gas: 8347)\n[PASS] test_onlyFactoryCanInitializeOnce() (gas: 26560221)\n[PASS] test_ownerControlsTreasuryAndTwoStepOwnership() (gas: 403025)\n[PASS] test_runtimeHasNoEscapeHatches() (gas: 4903196)\n[PASS] test_swapAmountBounds() (gas: 90102)\n[PASS] test_swapCannotUseAnotherPoolOrHookDataExemption() (gas: 125333)\n[PASS] test_uninitializedHookIsClearlyUnavailable() (gas: 26235203)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 612.28ms (290.78ms CPU time)\n\nRan 11 tests for test/FeeFlows.t.sol:FeeFlowsTest\n[PASS] testFuzz_fourSwapModesConserve(uint96,bool,bool,uint8) (runs: 256, μ: 543833, ~: 546007)\nLogs:\n  Bound result 404778573842692\n  Bound result 0\n\n[PASS] test_buyExactInputFundedAndFresh() (gas: 15698295)\n[PASS] test_buyExactOutputFundedAndFresh() (gas: 15652225)\n[PASS] test_factoryCanCollectAndWithdrawLiquidity() (gas: 1006140)\n[PASS] test_noLiquidityDoesNotCollectFee() (gas: 621815)\n[PASS] test_partialBuyExactOutputChargesOnlyActualInput() (gas: 498173)\n[PASS] test_partialNativeSpecifiedBuyRevertsWithoutCharge() (gas: 348380)\n[PASS] test_partialNativeSpecifiedSellRevertsWithoutCharge() (gas: 356773)\n[PASS] test_partialSellExactInputChargesOnlyFilledETH() (gas: 529315)\n[PASS] test_sellExactInputFundedAndFresh() (gas: 15874845)\n[PASS] test_sellExactOutputFundedAndFresh() (gas: 15880318)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 612.65ms (640.85ms CPU time)\n\nRan 9 tests for test/FeeBoundaryProperties.t.sol:FeeBoundaryPropertiesTest\n[PASS] testFuzz_beforeSwapRoundsWithoutConsumingTheTrade(uint128,uint8,bool,bool,bool) (runs: 1000, μ: 308964, ~: 457278)\nLogs:\n  Bound result 9\n  Bound result 14054\n\n[PASS] testFuzz_largestRepresentableNetETHOutputAndNextWei(uint8,bool) (runs: 1000, μ: 295403, ~: 139953)\nLogs:\n  Bound result 22\n\n[PASS] testFuzz_repeatedBuySellRoundTripsCannotCreateETH(uint96,uint8,uint8) (runs: 1000, μ: 1897612, ~: 1623531)\nLogs:\n  Bound result 575936635797937676\n  Bound result 1\n  Bound result 8\n\n[PASS] test_afterSwapRejectsGrossETHInputBeyondInt128() (gas: 301937)\n[PASS] test_afterSwapRejectsZeroOutputAndWrongSignedDeltas() (gas: 758467)\n[PASS] test_bothCallbacksRejectAllOutOfRangeSpecifiedAmounts() (gas: 741595)\n[PASS] test_dustInputWithNoOutputCannotBeCharged() (gas: 503349)\n[PASS] test_maximumNativeInputIsQuotedWithoutNarrowingOverflow() (gas: 51311)\n[PASS] test_zeroFeeAndFirstFeeWeiStillExecuteAndAllocate() (gas: 706081)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 611.72ms (1.40s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:ClaimsAccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 18\n  Bound result 999999990000000833\n  Bound result 6\n  Bound result 4357391075267662916\n  Bound result 1777\n  Bound result 999999990000010796\n  Bound result 120000\n  Bound result 999999990000000701\n  Bound result 120000\n  Bound result 18\n  Bound result 7149\n  Bound result 999999990000003783\n  Bound result 999999990000003536\n  Bound result 17\n  Bound result 11\n  Bound result 3\n  Bound result 999999990000004760\n  Bound result 2\n  Bound result 8172\n  Bound result 999999990000011659\n  Bound result 844910627036428753\n  Bound result 999999990000002080\n  Bound result 4584\n  Bound result 999999990000008802\n  Bound result 999999990079376899\n  Bound result 4727\n  Bound result 19\n  Bound result 999999990000005253\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.43s (2.42s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_assetsSplitsBurnsAndDeltasConserve() (runs: 128, calls: 4096, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | advance  | 774   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | buyback  | 831   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | claim    | 853   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | redeem   | 804   | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | swap     | 834   | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 18\n  Bound result 999999990000004014\n  Bound result 6\n  Bound result 999999990000002874\n  Bound result 999999990000005154\n  Bound result 18\n  Bound result 999999992592286114\n  Bound result 999999990000007143\n  Bound result 24\n  Bound result 11\n  Bound result 3\n  Bound result 999999990006191545\n  Bound result 2\n  Bound result 4280\n  Bound result 999999990000000576\n  Bound result 999999990000005617\n  Bound result 999999990000000954\n  Bound result 999999990079376899\n  Bound result 3398\n  Bound result 19\n  Bound result 999999990000019385\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.56s (2.55s CPU time)\n\nRan 3 tests for test/LifecycleInvariant.t.sol:LifecycleInvariantTest\n[PASS]\nLifecycleInvariantTest invariants:\n[PASS] invariant_everyTokenIsHeldOrActuallyBurned\n[PASS] invariant_receiptsBackBothAllocationsDespiteDonations\n[PASS] invariant_rolesLaunchClockAndFeeBoundsPersist\n LifecycleInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭------------------+----------------+-------+---------+----------╮\n| Contract         | Selector       | Calls | Reverts | Discards |\n+================================================================+\n| LifecycleHandler | accept         | 1416  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | advance        | 1342  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | buyback        | 1340  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | claim          | 1376  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | donate         | 1350  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | holderBurn     | 1372  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | nominate       | 1369  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | redeem         | 1392  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | redirect       | 1356  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | rejectedAction | 1353  | 0       | 0        |\n|------------------+----------------+-------+---------+----------|\n| LifecycleHandler | trade          | 2718  | 0       | 0        |\n╰------------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 99999999000000403\n  Bound result 55270438250833253\n  Bound result 99999999000002403\n  Bound result 0\n  Bound result 9185\n  Bound result 18571\n  Bound result 195940484968916\n  Bound result 823307522529\n  Bound result 9537864294657621015\n  Bound result 124\n  Bound result 31769\n  Bound result 99999999000005258\n  Bound result 3000\n  Bound result 17915\n  Bound result 70633373944213152\n  Bound result 6\n  Bound result 99999999010092096\n  Bound result 3425144841\n  Bound result 3303551950\n  Bound result 17068\n  Bound result 1000000002\n  Bound result 6870\n  Bound result 14544\n  Bound result 56261567337479303\n  Bound result 6856\n  Bound result 11115\n  Bound result 159363493615\n  Bound result 6\n  Bound result 8\n  Bound result 12318\n  Bound result 36028797018963968\n\n[PASS] test_handlerReachesAllStatefulPaths() (gas: 3688530)\nLogs:\n  Bound result 1000\n  Bound result 1000000000000000\n  Bound result 1000000000000000\n  Bound result 1000000000000000\n  Bound result 1000000000000000\n  Bound result 1000000000000\n  Bound result 1000000000000\n  Bound result 1000000000000\n  Bound result 1\n  Bound result 20\n\n[PASS] test_priceHistoryOracleSurvivesRingReuse() (gas: 14102982)\nLogs:\n  Bound result 16\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 16\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 16\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 16\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 3\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 1000\n  Bound result 8\n\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 13.02s (13.01s CPU time)\n\nRan 13 test suites in 13.03s (22.41s CPU time): 86 tests passed, 0 failed, 0 skipped (86 total tests)\n","passed":true},{"durationMs":244,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TidalHook.acceptOwnership()\",\"TidalHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"TidalHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TidalHook.claimTreasury()\",\"TidalHook.executeBuyback(uint256,uint256,uint160,uint256)\",\"TidalHook.receive()\",\"TidalHook.redeemClaims(uint256)\",\"TidalHook.setTreasury(address)\",\"TidalHook.transferOwnership(address)\",\"TidalHook.unlockCallback(bytes)\",\"TidalToken.approve(address,uint256)\",\"TidalToken.burn(uint256)\",\"TidalToken.transfer(address,uint256)\",\"TidalToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":23,\"README.md\":219,\"docs/SECURITY_REVIEW.md\":131,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TidalHook.sol\":431,\"src/TidalToken.sol\":19,\"test/AccessAndLaunch.t.sol\":208,\"test/AccountingInvariant.t.sol\":151,\"test/AtomicSettlement.t.sol\":117,\"test/BuybackTreasury.t.sol\":243,\"test/FailureAtomicity.t.sol\":264,\"test/FeeBoundaryProperties.t.sol\":204,\"test/FeeFlows.t.sol\":173,\"test/LifecycleInvariant.t.sol\":449,\"test/MathRegression.t.sol\":96,\"test/TESTING.md\":82,\"test/TidalToken.t.sol\":75,\"test/UnequalPriceFlows.t.sol\":76,\"test/Volatility.t.sol\":102,\"test/helpers/AtomicProbe.sol\":137,\"test/helpers/Fixture.sol\":122,\"test/helpers/HookState.sol\":63,\"test/helpers/TestRouter.sol\":108},\"excluded\":[\"deps/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"989927e87f44624b1aadd15b4c97147dbaafc0b3e24e2b62d0af064e16f50ca9","verifiedTreeHash":"92bf63ed3e10950006892f9977a586728d4457c1","verifierVersion":"0.1.0+3dcb4df2"}]}