shape:chain
Build LiquidityLockHook, a simple, creative Uniswap v4 hook: a hook that enforces a minimum liquidity hold: beforeAddLiquidity records the block per (PoolId, owner), and beforeRemoveLiquidity reverts unless at least N blocks (fixed at construction) have passed since that owner's most recent add in that pool. Tests cover the lock boundary, a second add extending the lock, and independence across pools. 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.
d3e9f5da0295352051454ee839b3cdb318258a64VERDICT: all checks passed
Wrote one **medium-severity** finding to [.imd-findings.json](.imd-findings.json): an unrelated user’s deposits through a shared router can repeatedly extend another LP’s withdrawal lock. Includes exact reproduction inputs, verified by source tracing. No tests were run; implementation files remain unchanged.
`LiquidityLockHook` is built as a vendored Foundry project. `forge build`, `forge test` (14 tests pass) and `forge fmt --check` all pass. I ran them with network access, and I did not re-run them with the network off. I did not run the protected suites from `.imd/reads`. Nothing is committed. - **Hook (`src/LiquidityLockHook.sol`):** `beforeAddLiquidity` records the block for the sender in that pool. `beforeRemoveLiquidity` reverts with `LiquidityLocked` until `lastAdd + MIN_HOLD_BLOCKS`, and the exact boundary block is allowed. `MIN_HOLD_BLOCKS` is fixed at construction. Only those two permission bits are set. - **Callbacks and identity:** every callback is restricted to the PoolManager. Identity is the `sender` the PoolManager passes; `hookData` is never read. - **No admin powers:** there are no owner or admin functions. - **Exits:** a zero-delta `modifyLiquidity` (fee collection) is never blocked. - **`afterInitialize`:** I skipped the dynamic-fee check because this design doesn't need one. - **Tests (`test/LiquidityLockHook.t.sol`):** they run against a real `PoolManager` with the vendored `PoolModifyLiquidityTest` and `PoolSwapTest` routers. They cover the boundary block, a second add extending the lock, and independence across pools and across owners. They also cover swaps being unaffected, `hookData` not spoofing identity, non-manager callers being rejected, and a fuzzed boundary. - **Vendoring:** `lib/` holds plain files with no submodules. v4-core is at commit `46c6834`, plus forge-std and solmate at their latest. I added `test/mocks/MockERC20.sol` because the protected test imports it. - **README:** covers the design, assumptions and operational responsibilities. - **Shared routers:** the lock applies to whichever address calls `modifyLiquidity`. If LPs share a router, one user's add extends the lock for everyone using that router in that pool. - **`HookFlags.sol`:** the protected test imports `src/HookFlags.sol`, which I did not provide. I assumed the verifier supplies it. - **Address mining:** the hook address must be mined for the two flag bits. Deployment is out of scope, so there is no script for it.
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.