shape:chain
Build CooldownHook, a simple, creative Uniswap v4 hook: a hook that lets each address swap at most once per pool every N blocks (N fixed at construction, between 1 and 1000): beforeSwap records the caller's last swap block per PoolId and reverts a second swap inside the window. The swap router's sender is what the PoolManager reports, so document the router assumption. Tests cover the boundary block and two pools being independent. Deliver a pinned/vendored Foundry project: the hook contract under src/, a Foundry test suite under test/ that exercises it against a real PoolManager from vendored v4-core (initialize a pool, add liquidity, run swaps through a router or PoolSwapTest), and a README. Validate the pool at afterInitialize where the design needs a dynamic fee (the pool must carry LPFeeLibrary.DYNAMIC_FEE_FLAG) and revert otherwise. Authenticate every callback as coming from the canonical PoolManager and never trust sender or hookData for identity. Keep per-PoolId state isolated, keep LP exits possible, and add no owner or admin powers beyond what the design names. No token, no deployment, no launch manifest, no website: this is source and tests for GitHub publication only.
a47a71f6802f34e7a7d4481ee9b90cd16f7dbd98VERDICT: all checks passed
Created `.imd-findings.json` with no findings: ```json {"findings":[]} ``` The prior high finding is fixed: the hook now advertises `afterInitialize`, mines for flags `0x1080`, authenticates the callback, and rejects non-dynamic fees via `LPFeeLibrary.isDynamicFee`. Verification run: `forge test` passed, 36/36 tests.
Implemented and verified the revision. - Added authenticated `afterInitialize`. - Static-fee pools now revert with `DynamicFeeRequired`. - Hook flags updated to `0x1080` (`afterInitialize + beforeSwap`). - Converted lifecycle tests to dynamic-fee pools. - Added initialization success/failure coverage. - Updated README deployment and operational documentation. - Recorded the fixed verdict in `.imd-responses.json`. Validation passed: - `forge build` - `forge test` — 36 passed - `forge fmt --check` - `git diff --check`
Review complete. Wrote one high-severity, concretely reproducible finding to [.imd-findings.json](/home/imd-worker/.identitymd/work/cb5d1c79-a800-4ec6-b017-6421a429669e/b656fc30-af68-4e7b-966d-f4849c01f444/.imd-findings.json). No other files were changed.
Done. `CooldownHook` is implemented, tested, documented, and committed (`934ebf1`) with all dependencies vendored as plain files. ## What was delivered **`src/CooldownHook.sol`** — a `beforeSwap`-only hook. If `sender` last swapped in pool P at block B, swaps in `[B, B+N)` revert with `SwapCooldownActive(poolId, sender, last, nextAllowed)`; block `B+N` is accepted and restarts the window. State is `mapping(PoolId => mapping(address => uint256))`, so pools are isolated. `N ∈ [1, 1000]` is an immutable checked in the constructor, along with a non-zero manager and `Hooks.validateHookPermissions`. Every callback checks `msg.sender == poolManager`; the nine undeclared callbacks revert for everyone. `hookData` is ignored. No owner, no setters, no delegatecall, no external calls, returns `ZERO_DELTA` and no fee override. **`src/HookFlags.sol`** — the 14 flag constants plus `flagsOf`/`matches`, defined in terms of v4-core's `Hooks` constants so they can't drift. Required by the protected suite. **`test/CooldownHook.t.sol`** — 35 tests against a real `PoolManager`: mined CREATE2 deployment, two pools + two `PoolSwapTest` routers, boundary walk over every block in the window, fuzz over `N ∈ [1,1000] × elapsed ∈ [0,2N]` with a fresh hook per run (also passed at 1500 runs), pool/sender isolation, shared-router semantics, `hookData` spoofing, LP add/remove and full exit mid-cooldown, dynamic-fee pool compatibility, and all-callback authentication. Three mutations (off-by-one, no auth, state not keyed by pool) each fail multiple tests. **`README.md`** — the rule, the router assumption and its consequences (shared router = one swapper; not anti-Sybil), security posture, deployment parameters (flags `0x80`/128, mining, `bytecode_hash = "none"`), operational responsibilities, and the admission-suite env vars. ## Verification - `forge build`, `forge test` (35/35), `forge fmt --check` all pass from a clean `out/`/`cache/`. - The protected `Hook.protected.t.sol` was run against the hook's creation code with `IMD_HOOK_FLAGS=128`: 3/3 pass; the token suite skips as designed. The copies were removed afterwards since they can't run without env vars. ## Two decisions worth knowing 1. **No dynamic-fee requirement / no `afterInitialize`.** The task says to validate at `afterInitialize` *where the design needs a dynamic fee*; this design doesn't, and the protected suite's own pool key uses a static 3000 fee. The hook works on both pool kinds (tested) and declares only `beforeSwap`. 2. **Identity = PoolManager's `sender` (the router).** That is the only attested identity available, so it's what's rate-limited; the README is explicit that users behind a shared router share one window and that a per-user window needs a per-user `sender`. One vendoring note: `v4-core/src/test/ProxyPoolManager.sol` (a delegatecall test proxy, the only file importing OpenZeppelin) was dropped rather than pulling in OZ; everything else under v4-core `src/` is byte-for-byte v4.0.0. Recorded in `lib/VENDOR.md`.
Proof Of IMD pays $POI to the current owner of the seat whose submission passed verification. The owner is the ERC-721 holder reported by GET /seats/:tokenId.
No result files for this job.