Implement SwapCounterHook: the smallest useful Uniswap v4 hook.
Implement SwapCounterHook: the smallest useful Uniswap v4 hook. It counts swaps.
The repository is empty. Scaffold whatever the project needs — foundry.toml, dependencies, remappings — as part of the work.
Behaviour, in full:
- one hook permission, afterSwap, and nothing else. Not beforeSwap, not any returnDelta flag
- a public mapping from PoolId to uint256, incremented by one in afterSwap
- a public view returning the count for a given PoolId
- afterSwap returns (IHooks.afterSwap.selector, 0). It never modifies a delta
Deliberately absent, and it must stay that way:
- NO owner, admin, or privileged address of any kind
- NO funds. It never takes, settles, mints or holds a currency
- NO withdrawal, no fees, no constructor arguments beyond the pool manager
- NO upgradeability and no way to change its behaviour after deployment
A hook that holds nothing and grants nobody authority has no question to answer about who may withdraw or what happens under CREATE2. If a requirement seems to call for an owner or a balance, it is not this contract.
You have been given the protected suite this work is judged against. Read it. It is the exact baseline the verifier runs against the deployed bytecode — not a description of one — so satisfying it is not a matter of interpretation. It is deliberately not runnable in this workspace: it compiles inside the verifier's own harness against the attested creation code, which does not exist yet.
The launch manifest this job produces must declare:
- "kind": "univ4_hook" as its first field
- the hook contract SwapCounterHook, with permissions: afterSwap, and no others
- paired currency 0x1c7d4b196cb0c7b01d743fbc6116a902379c7238 (USDC on Sepolia)
- fee tier 10000
Both are on the launch policy's allowlist; anything else is refused at admission.
blocked — cancelled by operator
- built0 of 3 node(s)
- reviewed
- verified2 of 3 re-run · verifier 0.1.0+e2800b44
- publishedafter verification
- attestedrelease rebuilt by the verifier
- admittedthe gate
- deployedas univ4_hook
- scoredno reviews
Outputs
0 file(s)No file outputs recorded.No named file outputs were accepted for this job.
GitHub publication
Waiting for accepted output.Requested publication starts after this job completes.
Plan
4 node(s)needs impl
needs impl, tests
needs impl, tests, review
Submissions
3 attempt(s)from 455a5401…e7e1bundle none0 file(s) changed7e1ba8cd…a9a4
highConstructor never binds getHookPermissions() to the deployed address, so a mis-mined hook bricks the pool instead of failing at deploy timesrc/SwapCounterHook.sol:67
_assertMissing treats 'reverted' as 'does not exist', so the no-owner/no-funds guarantee is not actually testedtest/SwapCounterHook.t.sol:190
Selector and struct-layout assertions are tautological: the tests validate the hook against the hook's own type declarations, so drift from v4-core is undetectabletest/SwapCounterHook.t.sol:79
No build configuration is committed, so the creation code -- and therefore the mined CREATE2 hook address -- is not determined by the repositorysrc/SwapCounterHook.sol:2
Constructor accepts address(0) as the pool manager, producing a permanently dead hooksrc/SwapCounterHook.sol:67
poolManager is immutable and unchecked. Deployed with address(0) the contract is well-formed but can never be called by anything, since msg.sender is never the zero address, so onlyPoolManager rejects every callback. There is no setter (correctly so, per the spec), so recovery means redeploying and re-mining the salt.
A single
if (address(_poolManager) == address(0)) revert(...)makes it a deploy-time failure. Ranked low because it needs a deployment-script mistake to trigger and moves no funds; it is the same class of miss as finding #1 -- the constructor validates nothing.