{"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":"69850bbd-f05e-40ac-af9b-e20a117fa877","kind":"skill:adversarial-review","nodes":[{"acceptedSubmissionHash":"d9c27b5f37dc5ad028d2930884c894054add0b2c861251fb14fae7991372fa84","dependsOn":[],"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"}],"objective":"Audit TollgateHook and TollToken from launch 168 (this commit) as an attacker, and explain the live Sepolia deployment facts below from the source. Read-only: report findings, change nothing. This is a Sepolia rehearsal with no real value; the requester plans a later production launch of the same design, which is why the findings matter.\n\nLive facts (read from Sepolia on 2026-09-27; the reviewer has no network, take them as given): the hook is at 0x1c8cf326d47eca16913cd84feb20ec1ee15440cc, whose low 14 bits are 0x00CC and match getHookPermissions(). Its immutable poolManager() returns 0x000000000004444c5dc75cB358380D2e3dE08A90 — the Ethereum-mainnet PoolManager literal in launch.json — and that address has no code on Sepolia. The factory initialised the ETH/TOLL pool (id 0x092d07044f8e00007eb4f18bd63beb5fb43690277da29223b026e832fd0a5649, fee 3000, tick spacing 60, hooks = this hook) and added one-sided TOLL liquidity on Sepolia's PoolManager 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543 in block 11779491. No Swap has been recorded on that pool since.\n\n1. From the source, state exactly what that mismatch does: which callbacks Sepolia's PoolManager will call on this hook, whether every swap reverts in onlyPoolManager (and with which wrapped error), whether liquidity can still be added and removed (the hook has no liquidity flags), whether payCreator and burnEth can ever do anything, and what is stranded. Give the root cause (manifest literal versus deployment chain; the constructor only rejects address(0); docs/deployment.md expected a fork rehearsal to catch a wrong literal) and a concrete fix for the next launch (the target chain's manager literal plus a constructor code-length or chain-id check, and a pre-launch swap rehearsal against the real manager), with a severity.\n2. Audit the logic as if bound to the right manager: the four swap modes and the sign of each returned delta (positive specified BeforeSwapDelta in beforeSwap, positive unspecified int128 in afterSwap), the PartialFill check against the raw pool delta, fee rounding (ETH legs under 50 wei pay nothing — can a trader split swaps to dodge the 2%?), the CREATOR_CAP boundary and CapReached firing once, burnEth's 5-block cooldown and MIN_BURN griefing, ERC-6909 claims donated to the hook, the transient lock on slot 1 across the swap's unlock and payCreator/burnEth's own unlock, and non-ETH pools attaching the same hook.\n3. Check TollToken for any mint, admin or burn path beyond what README.md discloses.\n\nReport each finding with a severity, file and line, and the exact call sequence that triggers it.","parentJobId":null,"planHash":"4fa779e6c3171d1f36c0b67c8b3673d98fc14e0cc044aa91b43388d89df66163","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"69850bbd-f05e-40ac-af9b-e20a117fa877","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-308-audit-tollgatehook-tolltoken-launch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51105","feedbackHash":"2afb5a3a2fb860e8346e4f0f96be275f9871149200b5b725298e4d6f33545792","nodeKey":"adversarial_review","submissionHash":"d9c27b5f37dc5ad028d2930884c894054add0b2c861251fb14fae7991372fa84","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"969e71d53bae17e7ba0191617250414b7b4daa3d97707f17d860bdfc53bdf4e3","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"03767c188b888bed","findings":[{"description":"The constructor rejects only address(0) and stores manager immutably (lines 111-115). launch.json:6 supplies the Ethereum-mainnet manager 0x000000000004444c5dc75cB358380D2e3dE08A90, but the supplied Sepolia deployment facts place the initialized ETH/TOLL pool on 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543 and establish that the configured address has no code there. The hook address 0x1c8cf326d47eca16913cd84feb20ec1ee15440cc correctly advertises 0x00CC: beforeSwap and afterSwap, with both return-delta permissions. Consequently, every otherwise valid swap from Sepolia's manager fails onlyPoolManager at line 130 during beforeSwap; afterSwap and pool execution are never reached. Hooks.callHook wraps OnlyPoolManager() as CustomRevert.WrappedError(hook, IHooks.beforeSwap.selector, abi.encodeWithSelector(TollgateHook.OnlyPoolManager.selector), abi.encodeWithSelector(Hooks.HookCallFailed.selector)); this explains the absence of Swap events. There are no initialization, liquidity or donation flags, so initialization and seeding can succeed and the position owner can still add/remove liquidity through the real manager, subject to normal position ownership and settlement. The seed is unusable for trading, not locked by this hook against LP withdrawal. No successful swaps means no accrued hook fees: payCreator reverts NothingDue; burnEth reverts TooSoon during its deployment cooldown and NothingToBurn afterwards. Neither can redeem ERC-6909 claims donated on the real manager, and donations never enter totalFees. Any such donated claims, tokens sent directly to the hook, or forcibly delivered ETH have no recovery path; no donations or accrued fee balance are established by the supplied live facts. The immutable hook/old pool cannot be retargeted. This is high severity for permanent loss of the launch pool's core functionality, not an allegation of stolen funds or real-value loss in the rehearsal. docs/deployment.md:43-48 explicitly required substituting the target chain's literal and relying on a fork rehearsal to catch this mistake. Local tests instead construct a fresh manager and bind the hook to it (test/helpers/Fixture.sol:44-47; test/LaunchRehearsal.t.sol:53,70), and test/TollgateHook.t.sol:120-125 explicitly accepts the empty mainnet address. The supplied protected hook checks etch manager code and omit the pool lifecycle, so passing them does not validate this chain/address pairing. For the next launch, use the target chain's actual manager literal (the given Sepolia manager for a Sepolia launch), reject a manager without code in the constructor and/or enforce the intended chain-ID/manager pairing, re-mine the CREATE2 salt, and deploy a new hook/pool. Require a pre-launch initialize/seed/buy/sell/payout/burn/unwind rehearsal against the real manager on the target-chain fork using the exact final manifest arguments; do not substitute a fresh or etched manager for that check.","line":112,"path":"src/TollgateHook.sol","reproduction":"Source-derived reproduction using the supplied live facts, without an RPC execution: let H = 0x1c8cf326d47eca16913cd84feb20ec1ee15440cc, M = 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543, and K be the initialized ETH/TOLL PoolKey (currency0 = address(0), currency1 = the launched TOLL token, fee = 3000, tickSpacing = 60, hooks = H) whose id is 0x092d07044f8e00007eb4f18bd63beb5fb43690277da29223b026e832fd0a5649. After its initialization and one-sided seeding in block 11779491, a trader calls a router bound to M with 1 ETH and SwapParams(true, -1000000000000000000, 4295128740). The router calls M.unlock(data); M invokes the router's unlockCallback; that callback calls M.swap(K, params, hex''). M checks the nonzero amount and initialized pool, then calls H.beforeSwap(router, K, params, hex''). H sets its transient lock, compares msg.sender = M with its immutable 0x000000000004444c5dc75cB358380D2e3dE08A90, and reverts OnlyPoolManager(). The outer error is WrappedError(H, 0x575e24b4, hex'f655705d', hex'a9e35b2f') with selector 0x90bfb865. Expected: the actual pool manager passes authentication and a fully fillable buy charges the disclosed fee. Actual: rejection before AMM execution, regardless of price/available liquidity, with all state and logs rolled back. Repeating with true/+1000000000000000000 (exact-out buy), false/-1000000000000000000 (exact-in sell), or false/+1000000000000000000 (exact-out sell), using 1461446703485210103287273052203988822378723970341 as the sell price limit, fails at the same callback. Zero-amount calls or calls outside an unlock can fail earlier in PoolManager; they are not exceptions that enable trading. Separately, the owner of a seeded position can call M.unlock -> M.modifyLiquidity(K, the same ticks/salt and a negative liquidityDelta) -> take the resulting positive token delta, because none of those steps invokes H. With the fee ledger still zero, H.payCreator() reverts NothingDue(), and H.burnEth() at block >= H.lastBurnBlock()+5 reverts NothingToBurn(). An account transferring its own 1 ETH of ERC-6909 claims via M.transfer(H, 0, 1000000000000000000) increases H's claim balance on M but does not change these outcomes or provide a recovery path. These are concrete consequences of the deployment mismatch and the disclosed donation ledger behavior, not evidence that a donation actually occurred.","severity":"high","title":"Mainnet PoolManager literal permanently disables the Sepolia launch pool's swaps"}],"hash":"d9c27b5f37dc5ad028d2930884c894054add0b2c861251fb14fae7991372fa84","nodeId":"007e4984-0600-490c-8f5b-e59c77f4c32a","outcome":"completed","summary":"Wrote **one high-severity finding** to [.imd-findings.json](/home/seat/.identitymd/work/69850bbd-f05e-40ac-af9b-e20a117fa877/007e4984-0600-490c-8f5b-e59c77f4c32a/.imd-findings.json). Source and configuration files remain unchanged.\n\n**High — the wrong immutable PoolManager disables trading.** The constructor accepts any nonzero manager at [TollgateHook.sol:112](/home/seat/.identitymd/work/69850bbd-f05e-40ac-af9b-e20a117fa877/007e4984-0600-490c-8f5b-e59c77f4c32a/src/TollgateHook.sol:112). The manifest supplies the mainnet literal, while the pool exists on Sepolia’s different manager.\n\nConcrete trigger: a router calls Sepolia’s manager through `unlock → swap(K, SwapParams(true, -1 ether, 4295128740), \"\")`. The manager invokes `beforeSwap`; `onlyPoolManager` rejects its address at line 130. The wrapped error is:\n\n```text\nWrappedError(\n  0x1c8cf326d47eca16913cd84feb20ec1ee15440cc,\n  0x575e24b4,         // beforeSwap selector\n  hex\"f655705d\",       // OnlyPoolManager()\n  hex\"a9e35b2f\"        // HookCallFailed()\n)\n```\n\nThe outer selector is `0x90bfb865`. All four otherwise-valid swap modes fail this way. Invalid calls can fail earlier in PoolManager. The `0x00CC` flags enable `beforeSwap`, `afterSwap`, and their return deltas; `afterSwap` is never reached here.\n\nInitialization, liquidity additions/removals, and donations invoke no hook callbacks. The position owner can therefore withdraw the seeded TOLL through normal position management. The pool is unusable for trading, but this hook does **not** trap LP principal.\n\nWith no successful fee accrual, `payCreator()` reverts `NothingDue`; `burnEth()` reverts `TooSoon` during the initial cooldown and `NothingToBurn` thereafter. Donated claims on the real manager, tokens transferred directly to the hook, and forcibly delivered ETH have no recovery path. The supplied facts establish no such donations or accumulated fee balance.\n\nThe deployment documentation explicitly expected replacing the literal when changing chains and catching mistakes in a fork rehearsal. Local lifecycle tests instead deploy a fresh manager; the protected checks omit the lifecycle. For the next launch, use the target chain’s manager, add a constructor code-length check and/or enforce the chain-ID/manager pairing, re-mine the salt, and redeploy. Rehearse swaps against the **real target-chain manager using the final manifest arguments** before launch.\n\nAssuming the correct manager, I found no additional substantiated defect:\n\n| Mode | Returned hook delta | Result |\n|---|---|---|\n| Exact-in buy, `amountSpecified = −E` | Positive specified `+floor(E/50)` before swap | Pool consumes `E−fee`; trader pays `E` |\n| Exact-out sell, `amountSpecified = +E` | Positive specified `+floor(E/50)` before swap | Pool produces `E+fee`; trader receives `E` |\n| Exact-out buy, tokens specified | Positive unspecified `+floor(raw ETH input/50)` after swap | Trader pays raw ETH plus fee |\n| Exact-in sell, tokens specified | Positive unspecified `+floor(raw ETH output/50)` after swap | Trader receives raw ETH minus fee |\n\nThese branches are at [TollgateHook.sol:201](/home/seat/.identitymd/work/69850bbd-f05e-40ac-af9b-e20a117fa877/007e4984-0600-490c-8f5b-e59c77f4c32a/src/TollgateHook.sol:201). PoolManager subtracts the positive hook delta from the caller’s delta. Minting the fee as claims creates an equal hook debit, so settlement balances.\n\n- **Partial fills:** Lines 236–238 check the **raw pool delta**. A `−1 ETH` exact-in buy requires raw `amount0 = −0.98 ETH`; `−0.97 ETH` reverts with wrapped `PartialFill`. Token-specified modes require raw `amount1 == amountSpecified`. Reversion rolls back the swap, fees, and logs.\n- **Rounding:** Line 274 floors each fee. Two fully filled 49-wei buys pay zero hook fee; one 98-wei buy pays one wei. Splitting can dodge the fee, but the rounding benefit is below one wei per component swap. Trading 1 ETH entirely in chunks of at most 49 wei requires at least **20,408,163,265,306,123 swaps**. This disclosed b","treeHash":null,"usage":{"cachedInputTokens":1245568,"inputTokens":107330,"model":"gpt-6-astra","outputTokens":9269,"runtime":"codex","turns":5,"wallClockMs":323048}}],"verification":[]}