{"assessments":[],"deployments":[],"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":"33016bad-ed32-4065-8c2e-269ce6ccc0a3","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"0a9e03cfeb65a3b92c3adbb630e50aeb991ef42ddd9c8b30bc88c1950983dc5c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"c26edc76dc7a76e09a42c3634054429c1679c6e247747d647a03f77e5b332d7e","skillId":"adversarial-review","tools":[]},"key":"adversarial_review","kind":"code","role":"review","skillHash":"c26edc76dc7a76e09a42c3634054429c1679c6e247747d647a03f77e5b332d7e","skillId":"adversarial-review","state":"accepted"},{"acceptedSubmissionHash":"da1aedbf035a3d9d0128344bb3c3f8d998582e8be128632431ad58a3e2cd61fd","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"fb6887b34514bcb194265fa403f0195c6a83eaef269da1e6189e1e9c4b372a7d","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"fb6887b34514bcb194265fa403f0195c6a83eaef269da1e6189e1e9c4b372a7d","skillId":"build-contract-project","state":"accepted"}],"objective":"Build SwapSizeCapHook, a simple, creative Uniswap v4 hook: a hook that caps a single swap at a fraction of the pool's liquidity: beforeSwap reverts if the specified amount exceeds one tenth of the current liquidity read from the PoolManager; the fraction is fixed at construction. Tests cover the cap in both directions and with exact output. 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.","parentJobId":null,"planHash":"12979e81206a93d8f1c5ce2325e8b4204ca259bd32e61fb570b570b2eef638b5","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"33016bad-ed32-4065-8c2e-269ce6ccc0a3","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/Identity-md/launch-73-build-swapsizecaphook-simple-creative"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50992","feedbackHash":"890ed4475341313d1df7fa0c993a28b47a0f82f65b17d242caf5dc24ad1c735b","nodeKey":"adversarial_review","submissionHash":"0a9e03cfeb65a3b92c3adbb630e50aeb991ef42ddd9c8b30bc88c1950983dc5c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50939","feedbackHash":"4532d0a98d3a72ebf6b2e93319d3d0d2f07b6002fbd13fbe2dae0d07a2a4e9c0","nodeKey":"build_contract_project","submissionHash":"da1aedbf035a3d9d0128344bb3c3f8d998582e8be128632431ad58a3e2cd61fd","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"c526dd310185ef2be0eaa9b456b2d6b96078cb5693c421b38549b1cc4b51e09d","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"62761ac059990ff7","findings":[{"description":"maxSwapAmount reads PoolManager.getLiquidity (active in-range L). If the price is in a range with L==0 (or L<10, where cap rounds to 0), every swap of any size reverts SwapTooLarge, even though out-of-range LP positions exist that the swap could reach. Follows literally from the spec, but it is a liveness limitation that the tests only partly cover (test_onlyInRangeLiquidityCounts).","line":74,"path":"src/SwapSizeCapHook.sol","reproduction":"Pool with one position [60,120] only; initialize at tick 0 (L=0). Swap exact-input 1 wei zeroForOne=false: cap=0, amount=1 > 0 -> reverts SwapTooLarge; the pool cannot be traded into the range. Same with L=9: cap=9*1000/10000=0, so a 1-wei swap reverts.","severity":"low","title":"Cap uses only current in-range liquidity: swaps revert whenever price sits in a liquidity gap"},{"description":"Design-inherent and documented in NatSpec. For pairs with differing decimals or skewed prices the cap is not a fraction of pool depth in either token, and the cap is per swap, so N swaps of cap size move N times more.","line":74,"path":"src/SwapSizeCapHook.sol","reproduction":"18-dec/6-dec pool where L=1e12: cap=1e11 raw units = 1e5 tokens of the 6-dec side but 1e-7 of the 18-dec side; two consecutive swaps each at cap succeed, moving 2x cap total (test_manySmallSwapsEachUnderCapSucceed shows this).","severity":"info","title":"Cap compares a token amount to L (sqrt(xy) units) and is trivially bypassed by splitting"}],"hash":"0a9e03cfeb65a3b92c3adbb630e50aeb991ef42ddd9c8b30bc88c1950983dc5c","nodeId":"0349fc56-64e4-4a2c-8464-a369e9700ea1","outcome":"completed","summary":"I found no high or medium defects, and I wrote two lower-severity findings to `.imd-findings.json`. I didn't edit any other file.\n\nThe 23 tests in `test/SwapSizeCapHook.t.sol` pass locally. I read `src/SwapSizeCapHook.sol` and the protected hook test. I didn't read the protected token test past its list of checks, because the hook has no token.\n\nWhat I checked and found sound:\n- Every callback uses `onlyPoolManager`, including the undeclared ones, which then revert with `HookNotImplemented`.\n- The absolute value of `amountSpecified` handles `type(int256).min` without overflow, and the same check applies to exact-input and exact-output swaps in both directions.\n- The permission flags match the address bits: only `beforeSwap` is declared, and the constructor validates it.\n- The hook keeps no per-pool state and has no owner or admin, and LP exits are unaffected because no liquidity callbacks are used.\n- `sender` and `hookData` are ignored.\n- No `afterInitialize` dynamic-fee check is needed, since this design doesn't use a dynamic fee.\n\nFindings, ranked:\n1. **Low:** the cap reads only current in-range liquidity. When the price sits in a range with zero liquidity, or liquidity below 10, the cap is 0 and every swap reverts, so the pool can't be traded back into a range that has liquidity. For example, a single position on [60,120] with the pool initialized at tick 0 rejects a 1-wei swap. This follows the spec literally, so I marked it a liveness limitation rather than a bug.\n2. **Info:** the cap compares a token amount against liquidity in sqrt(xy) units, and it applies per swap, so splitting a swap gets around it. The NatSpec already says this, but pairs with different decimals get an uneven cap on each side. The existing test `test_manySmallSwapsEachUnderCapSucceed` shows the splitting case.","treeHash":null,"usage":{"cachedInputTokens":170368,"inputTokens":10,"model":"claude-sonnet-5","outputTokens":2758,"runtime":"claude","turns":5,"wallClockMs":39699}},{"artifacts":[],"attempt":1,"bundleHash":"11da006c6f75950bd14ec53709822a590e0d6da79eb069b6c5a04012996358dc","device":"72b617d4b615473a","findings":[],"hash":"da1aedbf035a3d9d0128344bb3c3f8d998582e8be128632431ad58a3e2cd61fd","nodeId":"1afe1bc8-7229-4cf6-bb83-cf1868909a5b","outcome":"completed","summary":"I've built `SwapSizeCapHook`. `forge build`, `forge test` (23 of 23 pass) and `forge fmt --check` all pass after a `forge clean`. Nothing is committed: git has no author identity set here, and setting one would mean writing `.git/config`, which is off-limits. All the files are staged.\n\n**What it does**\n- In `beforeSwap`, the hook reverts with `SwapTooLarge(poolId, amount, cap)` when the swap amount is larger than `liquidity * CAP_BPS / 10_000`. Liquidity is read from the PoolManager each time. This applies in both directions and to both exact-input and exact-output swaps.\n- The fraction is set once at construction and can't be changed. 1_000 basis points gives the one-tenth cap; values outside 1–10_000 are rejected.\n- Every callback, including ones the hook doesn't use, reverts unless the caller is the PoolManager. `sender` and `hookData` are ignored, so no caller can get around the cap.\n- The hook stores nothing, so pools can't affect each other. It has no liquidity callbacks, so it can't stop LPs from adding or withdrawing.\n- There is no owner, setter, pause or upgrade path, and no `DELEGATECALL` or `SELFDESTRUCT`.\n\n**Where things are**\n- `src/SwapSizeCapHook.sol`: the hook.\n- `src/HookFlags.sol`: permission-bit helpers. The protected test imports this file.\n- `test/SwapSizeCapHook.t.sol` and `test/mocks/MockERC20.sol`: the tests. They run against a real v4-core `PoolManager` through its standard test routers.\n- `README.md`: design, assumptions, deployment parameters and operator responsibilities.\n- `foundry.toml` and `remappings.txt`: config, set to work offline with `ffi` off.\n- `lib/`: v4-core, solmate and forge-std copied in as ordinary files at pinned commits, listed in `lib/VENDORED.md`. From v4-core I kept only the source plus one test helper its routers need.\n\n**What the tests cover**\n- A swap exactly at the cap succeeds and one unit over reverts, for both directions and both swap types.\n- A fuzz test checks the cap boundary with random amounts.\n- A rejected swap leaves the pool price unchanged.\n- The cap grows and shrinks as liquidity is added and removed, and an LP can withdraw everything, after which every swap reverts.\n- Liquidity outside the current price range doesn't count towards the cap.\n- Two pools on one hook keep separate caps.\n- Direct calls that don't come from the PoolManager are refused, and `sender` or `hookData` give no exemption.\n- The largest possible negative amount doesn't overflow.\n- The hook refuses bad constructor values and refuses to deploy at an address carrying the wrong permission bits.\n\n**Protected suite:** I ran `Hook.protected.t.sol` in `test/scratch/` against the real creation code, with flags set to 128 (`BEFORE_SWAP`) and one tenth as the fraction. All 3 tests pass. The token suite skips itself because there's no token.\n\n**Decisions and limitations**\n- **No dynamic fee.** The task only asks for that check where the design needs a dynamic fee, and this one doesn't. So there's no `afterInitialize` check and the hook works with any fee tier. The protected test itself uses a fixed 3000 fee.\n- **Units don't match.** As specified, the cap compares a token amount with the pool's liquidity figure, which is not a token amount. The resulting limit only works out to roughly 10% of reserves in one case: price near 1, liquidity spread across the full range, same decimals. Otherwise it can be much tighter or looser, so the fraction should be chosen per pool. The README explains this.\n- **Easy to get around.** The cap is per swap, so a trader can split one large trade into several swaps. Someone can also add liquidity just before a swap to raise the cap, then remove it. The README says so.\n- **Compiler must be present.** The offline verifier needs solc 0.8.26 already installed; a compiler can't be committed to the repo. I had to download it once to build here.\n- **Not audited.** Passing tests are not a security audit, and an independent review is advisable before real use.","treeHash":"064cbe196d2bcc8032e375951b5a4fe9ec7005d7","usage":{"cachedInputTokens":932209,"inputTokens":32,"model":"claude-opus-5","outputTokens":22192,"runtime":"claude","turns":18,"wallClockMs":215675}}],"verification":[{"checks":[{"durationMs":1809,"exitCode":0,"name":"build","output":"Compiling 75 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.71s\nCompiler run successful!\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/SwapSizeCapHook.t.sol:296:42\n    │\n296 │         int256 specified = exactInput ? -int256(amount) : int256(amount);\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/SwapSizeCapHook.t.sol:296:59\n    │\n296 │         int256 specified = exactInput ? -int256(amount) : int256(amount);\n    │                                                           ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\n","passed":true},{"durationMs":184,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 23 tests for test/SwapSizeCapHook.t.sol:SwapSizeCapHookTest\n[PASS] testFuzz_capBoundary(uint256,bool,bool) (runs: 512, μ: 113982, ~: 137456)\nLogs:\n  Bound result 19050854529796673824\n\n[PASS] test_capGrowsWithAddedLiquidity() (gas: 314868)\n[PASS] test_capShrinksWhenLiquidityLeaves_andLPsCanAlwaysExit() (gas: 236098)\n[PASS] test_capsArePerPool() (gas: 427521)\n[PASS] test_constructorRejectsAddressWithWrongFlags() (gas: 17628)\n[PASS] test_constructorRejectsInvalidCap() (gas: 28043)\n[PASS] test_constructorStoresImmutables() (gas: 21493)\n[PASS] test_create2MinedDeploymentWorks() (gas: 35816296)\n[PASS] test_directCallbacksFromNonManagerRevert() (gas: 35436)\n[PASS] test_exactInput_oneForZero_aboveCapReverts() (gas: 64624)\n[PASS] test_exactInput_oneForZero_atCapSucceeds() (gas: 137672)\n[PASS] test_exactInput_zeroForOne_aboveCapReverts() (gas: 64658)\n[PASS] test_exactInput_zeroForOne_atCapSucceeds() (gas: 149158)\n[PASS] test_exactOutput_oneForZero_aboveCapReverts() (gas: 64314)\n[PASS] test_exactOutput_oneForZero_atCapSucceeds() (gas: 138308)\n[PASS] test_exactOutput_zeroForOne_aboveCapReverts() (gas: 64303)\n[PASS] test_exactOutput_zeroForOne_atCapSucceeds() (gas: 143180)\n[PASS] test_hookDataAndSenderGrantNoExemption() (gas: 25935)\n[PASS] test_int256MinDoesNotOverflow() (gas: 25608)\n[PASS] test_manySmallSwapsEachUnderCapSucceed() (gas: 428089)\n[PASS] test_onlyInRangeLiquidityCounts() (gas: 254756)\n[PASS] test_revertedSwapLeavesPoolUntouched() (gas: 70241)\n[PASS] test_undeclaredCallbacksRefuseEvenFromManager() (gas: 18661)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 78.57ms (116.47ms CPU time)\n\nRan 1 test suite in 79.76ms (78.57ms CPU time): 23 tests passed, 0 failed, 0 skipped (23 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"da1aedbf035a3d9d0128344bb3c3f8d998582e8be128632431ad58a3e2cd61fd","verifiedTreeHash":"064cbe196d2bcc8032e375951b5a4fe9ec7005d7","verifierVersion":"0.1.0+eab70f1b"}]}