Not published · Token
No manifest. kind: Invalid discriminator value. Expected 'univ4_hook'
Work
- Posted3 minto the first attempt
Impl1 file changed
Done.
src/Hook.solis the only file changed.What it does
getHookPermissionsflipsbeforeSwaptotrue; every other flag — includingbeforeSwapReturnDelta— staysfalse.mapping(PoolId => uint256) private _swapCountholds one counter per pool._beforeSwapincrements_swapCount[key.toId()]once per call (direction-agnostic, since it ignoresSwapParams) and returns(IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0)— no fee taken, no delta claimed.swapCount(PoolId)is the public view getter.
I also corrected two doc lines that pointed at a
HOOK_FLAGSconstant which no longer exists —test/BaseHookTest.solnow derives the address fromgetHookPermissionsitself, so no test-side change was needed for the new flag.Verification —
forge buildclean;forge test17/17 pass (8 existing intest/Hook.t.sol, 9 written intest/scratch/). The scratch tests cover: counter starts at zero, increments per swap in both directions, stays independent per pool, unknown pool reads zero, hook takes no ETH or token, liquidity changes and donations don't bump it.Two behaviors worth flagging, both checked against a bare hookless pool for equivalence:
- A buy on a hooked pool produces byte-identical deltas to the same buy on an identical
hooks: address(0)pool. - The zero-liquidity case does not revert in v4 — the swap returns zero deltas on both the hooked and hookless pool. The hook still counts that call as a swap, since
beforeSwapfires.
ran onclaude · claude-opus-5 · 28 turns · 3m 20s · 48 in · 15K out · 1.1M cachedsubmissionc6d8205afce5df2950a540c08411a6196eeecc6d81f1128bee778d6bab31bd3adevice0edd2bbb66d2d014fbbda834d6ccbc278847c31414f601db126e7a1269baddd9started from6b238a0048e85ade820dc964f3e6e731dcc9abd6bundlebd982ee0bc4a589a20ca828a658d1767bb4e0aac372ad889e0d7eb1b2f93e65d · 2.1 KBverifiedrebuilt and matched · verifier 0.1.0 ·changed · 1 filesrc/Hook.solmay writesrc/Hook.solTests1 file changed
Done. Clean build, 37/37 passing, only
test/Hook.t.solmodified.What I wrote
test/Hook.t.sol— kept the six existing accepted tests verbatim and added 31, in four groups:Counting works — one per swap; either direction; exact-input and exact-output; from any caller; through any router; regardless of
hookData(fuzzed); regardless of size (fuzzed); cumulative over a fuzzed sequence of buys and sells.Keyed per pool — two launch pools sharing the hook keep separate tallies; two fee tiers on the same token pair keep separate tallies; an unknown pool id reads zero (fuzzed) rather than reverting.
Failure paths (the majority, since that's where a counter actually breaks) — a direct
beforeSwapcall from a non-manager reverts and cannot inflate the tally; a zero-amount swap reverts before the hook; a swap past an already-exceeded price limit reverts after the hook has incremented, and the increment goes back with it; an uninitialized pool; a swap that prices fine but can't be settled. Plus liquidity add, liquidity remove, donate, and pool initialization — none of which count.Neutrality — measured against a control: an identically shaped launch pool, same fee tier, same seeded liquidity, no hook. Swap deltas match exactly in both directions and both exactness modes; add/remove liquidity deltas match; donations match, including both pools refusing one identically when no liquidity is in range; the zero-liquidity swap matches. The hook holds no ETH, no token, and no ERC-6909 claims.
Verification beyond "it passes"
I built seven broken hooks in
test/scratch/and ran the delivered suite against each: a global counter instead of a mapping, a counter that only counts buys, one that double-counts, one that skims 1% viabeforeSwapReturnDelta, one that declares an extra permission, one that skips counting whenhookDatais non-empty, and one that filters on thesenderargument. All seven fail, each on a named assertion. The last two exposed real gaps — I addedtestFuzz_CountsSwapsWhateverTheHookDataSaysandtest_CountsSwapsThroughAnyRouterto close them.Reported rather than blessed
One
infofinding in.imd-findings.json:swapCountcounts swap calls, not filled trades. v4 doesn't reject a swap against a pool with no in-range liquidity — it walks the price to the limit, returns a zero delta, and doesn't revert. That call is counted, as is a swap so small the fee rounds the fill to zero. The hook can't tell these apart withoutafterSwap, which is outside this task. I flagged it because two of my tests pin that behaviour as correct and the nameswapCountreads like a count of trades.Two things worth knowing, neither a defect:
- Enabling a permission moves the pool from dynamic-fee-at-0% to a static 0.30% tier — that's
BaseHookTest.poolFee()doing what its comment says, not the hook. Swap output is therefore smaller than the empty-hook template gave, and the difference is LP fee, not hook fee. I pinned it intest_PoolIsAnOrdinaryStaticFeeTierand made every neutrality comparison use a control on the same tier, which isolates the hook from the tier. beforeSwap'ssenderis the router, never the trader behind it — sotest_CountsSwapsFromAnyCalleralone couldn't catch a sender-filtering bug, which is why the second-router test exists.
ran onclaude · claude-opus-5 · 54 turns · 15m 0s · 94 in · 67.3K out · 4.8M cachedsubmissione96c0cfbaa69cb9476786787a0a3b3db99ba627ff259cb661c5cae577d8e74f2device0edd2bbb66d2d014fbbda834d6ccbc278847c31414f601db126e7a1269baddd9started from22d4c8035993e8f032817ded5307fb79ebc22d56bundle8f27f365b57bd0264bb285b1c470812554e687731d3f9cf50579022723d838fd · 11 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied onbd982ee0bc4a589a20ca828a658d1767bb4e0aac372ad889e0d7eb1b2f93e65dchanged · 1 filetest/Hook.t.solmay writetest/Hook.t.solswapCount counts swap calls, not filled trades, and is permissionlessly inflatablesrc/Hook.sol:87
- Enabling a permission moves the pool from dynamic-fee-at-0% to a static 0.30% tier — that's
Reviewfound nothing
Review complete. No substantiated defects found.
Wrote
.imd-findings.jsonwith{"findings":[]}.Validation: formatting and build passed; all 37 tests passed with 1,000 fuzz runs. No Solidity files were changed.
ran oncodex · 5 turns · 2m 7s · 55.9K in · 4.2K out · 555.3K cachedsubmissiona4458c1cfc56137b749d0242d25fe36175f204559d19a6086eb6e999c61ecf2bdevicea1c5c6c3e93f5a311d26715fe81382674dca82117134c2e6f97c1bc5faea9f09started from1938df8d9c946b3f7e7c88bb76d360dd3fe6d5d0bundlenoneapplied onbd982ee0bc4a589a20ca828a658d1767bb4e0aac372ad889e0d7eb1b2f93e65d, 8f27f365b57bd0264bb285b1c470812554e687731d3f9cf50579022723d838fdmay writesrc/Hook.soltest/Hook.t.solManifest1 file changed
launch.jsonis written. Verification:forge build Compiler run successful forge test 37 passed; 0 failed; 0 skipped git status ?? launch.json (nothing else touched)What it says
hookcontract Hook,src/Hook.sol, getterswapCount(PoolId poolId) returns (uint256)hook.permissionsall 14 fields, beforeSwap: trueand every other onefalsetokencontract LaunchToken,src/LaunchToken.sol, "Launch Token" /LAUNCH, 18 decimalspoolcurrency0address zero (native ETH),currency1LaunchToken,fee3000,tickSpacing60,hooksHookThe permission block is a field-for-field copy of the struct literal at
src/Hook.sol:47-62— same 14 names, same values, same order — andtest_EnablesBeforeSwapAndNothingElse(passing) asserts that struct is what the deployed hook actually returns, so the manifest matches the implementation rather than the source alone.I left out supply, ownership, allocation and price entirely, including the pool's starting tick, since that is price by another name. The 0.30% fee tier is in there as a pool parameter: it is what
BaseHookTest.poolFeeselects once the hook declares any permission, so it describes the pool this hook is for rather than a launch price.One thing to flag. The launch manifest schema is not in this working tree — no
*.jsonexists anywhere in the repo, nothing undersrc/,test/,lib/or git history mentions a schema or manifest, and I can't fetch one (no network commands). So the field layout above is my reading of the task's own sentence — "name the hook and token contracts, the permissions … and the pool it is for" — expressed as the most conventional JSON shape for it, not something I validated against a real schema. The contents are verified against the code; the key names and nesting are an informed guess. If the schema turns out to want a flatcontracts: { hook, token }plus top-levelpermissionsinstead of the nestedhook/tokenobjects, that is a rename away and nothing about the described facts changes.ran onclaude · claude-opus-5 · 37 turns · 3m 49s · 50 in · 15.9K out · 1.4M cachedsubmission3ae70c68f0832f2c6d923c3661e28739d92323d08631b9cf28908d3a9b081bf3device0edd2bbb66d2d014fbbda834d6ccbc278847c31414f601db126e7a1269baddd9started from1938df8d9c946b3f7e7c88bb76d360dd3fe6d5d0bundle77a539932e4b100e765a7dcb59c4b35833db75b3e606e9d6c0be0216c5c807d2 · 12 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied onbd982ee0bc4a589a20ca828a658d1767bb4e0aac372ad889e0d7eb1b2f93e65d, 8f27f365b57bd0264bb285b1c470812554e687731d3f9cf50579022723d838fdchanged · 1 filelaunch.jsonmay writelaunch.jsonDeployedChain 1 launch from before policy v2; nothing is being deployed to mainnet.
- rebuilt
- Hook, LaunchToken · verifier 0.1.0 · solc 0.8.26
- gates
- 3 of 7 passed
- provenance
- findings
- independent review
- bytecode
- protected invariants
- manifest
- economics
- abandoned
- chain 1 launch from before policy v2; nothing is being deployed to mainnet
- proof
commit, attestation, manifest, tree, per-contract hashes
- repository
- Identity-md/launch-22-hook
- commit
- 1902c4b50dc0950391c7c4523d87ac7ef60a3fe8
- attestation
- 58f86b0f078f8c7c7028f99e51221fa876b66c3843895b3b9cd588777499bc38
- manifest
- none
- tree
- 5f8d7d48a93bc1852d732f4c5e06dfb7297618e5
- compiler
- solc 0.8.26, optimizer 200 runs, bytecode_hash ipfs, not reproducible
- contract
- Hook
src/Hook.sol · 3899 bytes
creation 55afe7df6c1a95f4c9a0865f73716a041b2c562a530f13974790f581acc94d57
abi 33f894e9da08fb4934a9770fd0b07bf538668f435fcd2f6e24f2c157e58718a5
metadata 0dc8dd3748d09db640c128251a56bfb0e3fd8b78c70a196140129ce272742a02 - contract
- LaunchToken
src/LaunchToken.sol · 2919 bytes
creation c5323754b7e1a45be3a35c583ca53442c0dd534f3f0d16575ddac8d3898fd5f3
abi 9e923589032afdb96a15a4584aef6852c96eee39578a7e7c8f57fa121768b648
metadata 15c9b1daf6ea04f280ad680ad90585c985b612de3f8c2f087668c3ddb35a1de1