{"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":"0f143654-9763-4fe7-a0c4-aca032047b3b","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"3946453f52cf9419ddcfcd7c02aa17903f0169d3c6e44d6e6ef3ede9cdd08873","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3d4abf49c12bb3b31d814494e116b75c034bb17a8cc667b6ce4f3787d43bcd4b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"eb2ea0465f178e3f8f7dd757370459d0cd2ac159681199335d1fe35e9ee3cd53","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"4041a9925aac6fd4802e719a56a058fa895c7547bdf653ee36b90d67ea618b44","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"8aa0a0c3887098080f8c4420cf6c2df96f4f67b0d6f7652b846cbad1129dc319","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"abcbfdfc52baebc0dc59eb72cc19f429b8807fb3b5c12319efe41b19a205278b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"e9b5b149e37cb8a40fd059baa03a4f1e6b2e65bacfb165335ba649656d0c8e94","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"dbfabc91e487b8c4335431e160840684c3ae4a92d17a41f80911d53ae23b953d","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Token name: Work\nToken symbol: WORK\nWrite these files EXACTLY as given (tested; formatting only, never change logic; launch.json must keep its notes string). foundry.toml: solc 0.8.26, evm_version cancun, optimizer true, optimizer_runs 200, via_ir true, bytecode_hash \"none\"; vendor Uniswap v4-core v4.0.0 at lib/v4-core and OpenZeppelin v5 at lib/openzeppelin-contracts, remappings v4-core/=lib/v4-core/ and @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/. Hook deploys at a CREATE2 address with flags 0x2044. Fees: snipe guard 50% sliding to 2% over 15 min after pool init, then 2%, to the treasury 0xc9eafe33a510a3a3d95a94c4f85adaf6a3ea12a0 via permissionless sweep. Admin: that address may only set the standing fee within 0-10%. Tests against the real PoolManager.\nlaunch.json:\n{\"kind\":\"univ4_hook\",\"hook\":{\"contract\":\"WorkLaunchHook\",\"constructorArgs\":[\"$poolManager\",\"$token\"],\"permissions\":[\"beforeInitialize\",\"afterSwap\",\"afterSwapReturnDelta\"]},\"token\":{\"contract\":\"Work\",\"name\":\"Work\",\"symbol\":\"WORK\",\"decimals\":18},\"pool\":{\"pairedCurrency\":\"0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127\",\"fee\":12500,\"tickSpacing\":60,\"initialPrice\":\"79228162514264337593543950336\"},\"notes\":\"Work (WORK) is a plain fixed-supply OpenZeppelin ERC-20: 1,000,000,000 with 18 decimals minted once to msg.sender, no admin. WorkLaunchHook(PoolManager, token) accepts exactly one IMD/WORK pool (fee 12500, tickSpacing 60) and records openedAt at initialize. Every swap pays a hook fee on its unspecified side: 50% at openedAt sliding linearly to the standing fee over 900 s, then the standing fee (2%; 0xc9eafe33a510a3a3d95a94c4f85adaf6a3ea12a0 may set 0-10%). Fees accrue as PoolManager claims and go to 0xc9eafe33a510a3a3d95a94c4f85adaf6a3ea12a0 via a permissionless sweep(). Flags 0x2044. No other admin, no upgrades.\"}\nsrc/Work.sol:\npragma solidity 0.8.26;import{ERC20}from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";contract Work is ERC20{constructor()ERC20(\"Work\",\"WORK\"){_mint(msg.sender,1_000_000_000 ether);}}\nsrc/WorkLaunchHook.sol:\n// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;import{IPoolManager}from \"v4-core/src/interfaces/IPoolManager.sol\";import{IHooks}from \"v4-core/src/interfaces/IHooks.sol\";import{Hooks}from \"v4-core/src/libraries/Hooks.sol\";import{PoolKey}from \"v4-core/src/types/PoolKey.sol\";import{Currency}from \"v4-core/src/types/Currency.sol\";import{BalanceDelta}from \"v4-core/src/types/BalanceDelta.sol\";contract WorkLaunchHook{address public constant IMD=0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;address public constant B=0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0;IPoolManager public immutable poolManager;address public immutable token;uint256 public openedAt;uint256 public standingFee=200;event StandingFee(uint256 fee);constructor(IPoolManager m,address t){require(t!=IMD&&t!=address(0));poolManager=m;token=t;Hooks.validateHookPermissions(IHooks(address(this)),Hooks.Permissions(true,false,false,false,false,false,false,true,false,false,false,true,false,false));}modifier onlyPM(){require(msg.sender==address(poolManager));_;}function beforeInitialize(address,PoolKey calldata k,uint160)external onlyPM returns(bytes4){address a=Currency.unwrap(k.currency0);address b=Currency.unwrap(k.currency1);require(openedAt==0&&((a==IMD&&b==token)||(a==token&&b==IMD))&&k.fee==12500&&k.tickSpacing==60&&address(k.hooks)==address(this));openedAt=block.timestamp;return IHooks.beforeInitialize.selector;}function feeNow()public view returns(uint256){uint256 t=openedAt;uint256 s=standingFee;if(t==0||block.timestamp<=t)return 5000;t=block.timestamp-t;return t<900?s+(5000-s)*(900-t)/900:s;}function setStandingFee(uint256 f)external{require(msg.sender==B&&f<=1000);standingFee=f;emit StandingFee(f);}function afterSwap(address,PoolKey calldata k,IPoolManager.SwapParams calldata p,BalanceDelta d,bytes calldata)external onlyPM returns(bytes4,int128){bool u1=(p.amountSpecified<0)==p.zeroForOne;int128 a=u1?d.amount1():d.amount0();uint256 f=uint256(int256(a<0?-a:a))*feeNow()/10000;if(f>0)poolManager.mint(address(this),(u1?k.currency1:k.currency0).toId(),f);return(IHooks.afterSwap.selector,int128(int256(f)));}function sweep()external{poolManager.unlock(\"\");address[3]memory c=[IMD,token,address(0)];for(uint256 i;i<3;++i){Currency x=Currency.wrap(c[i]);uint256 v=x.balanceOfSelf();if(v>0)x.transfer(B,v);}}function unlockCallback(bytes calldata)external onlyPM returns(bytes memory){for(uint256 i;i<2;++i){Currency x=Currency.wrap(i==0?IMD:token);uint256 v=poolManager.balanceOf(address(this),x.toId());if(v>0){poolManager.burn(address(this),x.toId(),v);poolManager.take(x,B,v);}}return \"\";}}\n","parentJobId":null,"planHash":"6db68c372bb98f02888cba4bb3a8bde972e8c556d9fd63328a11c3be4499fa52","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"0f143654-9763-4fe7-a0c4-aca032047b3b","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1002-src-work-sol-src-worklaunchhook-sol"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51082","feedbackHash":"d0ea6bbe38305fbc84ebe15a481c0c59491cbb2ba9ea810ef5ea4f8569a8aedc","nodeKey":"audit_economics","submissionHash":"3946453f52cf9419ddcfcd7c02aa17903f0169d3c6e44d6e6ef3ede9cdd08873","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50986","feedbackHash":"8bfc3f4c2da3e02518a3d106275be9bf0167c6f16c0cadae8848f337682565a7","nodeKey":"audit_flow","submissionHash":"3d4abf49c12bb3b31d814494e116b75c034bb17a8cc667b6ce4f3787d43bcd4b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51481","feedbackHash":"ccf4ec36480609414d4ad9d4dcec1c7e08a472157e57d40ac33ec2670dbdb03d","nodeKey":"audit_judge","submissionHash":"eb2ea0465f178e3f8f7dd757370459d0cd2ac159681199335d1fe35e9ee3cd53","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51511","feedbackHash":"b0a97cff04817c3ba05a9576d5b3d2ec4a330ea2cbe221b64000c55d2945b0cc","nodeKey":"audit_judge","submissionHash":"4dfaaf01ff0372a9c917cf295e2e06e268fa001af4f14e1be5ffd19b0560a5ee","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51140","feedbackHash":"a9d71a5f7ea2fd24d7485ecce7cce9040e53f951395a4e15033bccc722255586","nodeKey":"audit_math","submissionHash":"4041a9925aac6fd4802e719a56a058fa895c7547bdf653ee36b90d67ea618b44","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52178","feedbackHash":"2fc460b475cf0123acaa894d4cab9a9865a7d4f907860fa58343e22da641b303","nodeKey":"audit_permissions","submissionHash":"8aa0a0c3887098080f8c4420cf6c2df96f4f67b0d6f7652b846cbad1129dc319","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51307","feedbackHash":"66ad2a0187419ae9a99978af67baba5021436fc943e0d17ee2af1f5bd904b1f8","nodeKey":"build_contract_project","submissionHash":"2ee3f6e64c71856fcf67f186a03337f8428af09e68f88440e30373ef5bcf3aa9","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50960","feedbackHash":"b0e6d87cf045a3fd001994ad513555fc6e74cfebbae62d8deee0fb7e7b6d5994","nodeKey":"build_contract_project","submissionHash":"abcbfdfc52baebc0dc59eb72cc19f429b8807fb3b5c12319efe41b19a205278b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51173","feedbackHash":"147a76fb3fb625e10883b43f97784e670478319b060179037bd0891665cef406","nodeKey":"manifest","submissionHash":"e9b5b149e37cb8a40fd059baa03a4f1e6b2e65bacfb165335ba649656d0c8e94","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51099","feedbackHash":"77bb5660242797b700a11802a587c9a31b88ce2101ad35d82eeb2a40e4044778","nodeKey":"manifest","submissionHash":"4cd5b5ff1e545f34f20bf7b5b909d979c346090b6e070069fcafafd15267c9df","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52177","feedbackHash":"5c28ec9537299356a6863e00cbe58032f7d7f91efa31ee9beeb86e9a03b6570a","nodeKey":"write_foundry_tests","submissionHash":"dbfabc91e487b8c4335431e160840684c3ae4a92d17a41f80911d53ae23b953d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51151","feedbackHash":"d0e3e7d70c30c70c0b2ed2a940e3c32c665d2ef536cc3d2924c9996fe366f436","nodeKey":"write_foundry_tests","submissionHash":"e26241886bf3ae474d2d0aeb262f93c1cfc5aae8af364b3d1e6971a08df1787d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"83b33cecc2e0342a45962b553dd648eb0aeea87482a18fc72bc00133510f371f","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"79ae6d6fa4093b28446a51e6d2975c71e4c1fba79f49b7c248fd6c6a6632fe82","device":"51908b9b0306f441","findings":[],"hash":"2ee3f6e64c71856fcf67f186a03337f8428af09e68f88440e30373ef5bcf3aa9","nodeId":"f4100132-a8eb-4f99-a629-53c1998693b9","outcome":"completed","summary":"Implemented with supplied logic and launch notes preserved. Dependencies, CREATE2 utilities, tests, and operational documentation are included.\n\nVerified:\n- `forge build` and `forge fmt --check` pass.\n- All 41 tests pass against the real PoolManager.\n- Second-seed fuzzing passes at 1,024 runs.\n- Offline deployment-script simulation passes.\n\nReview limits and deployment responsibilities are documented. No transactions were broadcast.","treeHash":"3e84dbdf3c116a61872ead60adff0c4e6f3b1b47","usage":{"cachedInputTokens":1361536,"inputTokens":104585,"model":"gpt-6-astra","outputTokens":24196,"runtime":"codex","turns":7,"wallClockMs":687798}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f47f953f8e35921e","findings":[{"citation":"resolved","description":"afterSwap charges f = |unspecified| * feeNow()/10000 in every swap mode. For an exact-input buy the unspecified side is the WORK output, so the buyer keeps (1 - r) of it: effective rate r. For an exact-output buy the unspecified side is the IMD input, so the buyer pays (1 + r) times the pool input: effective rate r/(1+r). At the launch rate r = 50% that is 33.3%; at the 10% maximum standing fee 9.09%; at 2% 1.96%. The same WORK amount therefore costs 25% less IMD when requested as exact output during the launch window. Nothing in the hook prevents a sniper from always submitting exact-output orders (any router supports amountSpecified > 0), so the 50% snipe guard the manifest describes is in practice a 33% guard and the treasury collects one third less than the headline rate on every exact-output purchase. Who profits: any trader using exact-output; who loses: the treasury (0xc9ea...12a0) and the launch's price protection. Invariant guide: path divergence (two routes to the same outcome, different fee). Minimal fix that keeps the 'fee on the unspecified side' design: when a < 0 (exact-output, trader pays the unspecified side) charge f = |a| * fee / (10000 - fee) so the trader's total input is input/(1 - r) and both routes lose the fraction r. This is a change to the fee formula the brief fixed as 'never change logic', so it needs the author's scope decision; the alternative is to document the asymmetric rate.","line":67,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\n/// @dev Stand-in for the fixed IMD address; etched there in setUp.\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\n/// Exact-input and exact-output purchases of the same WORK amount must lose the same\n/// fraction to the hook. Today exact-input loses feeNow()/10000 of the output while\n/// exact-output loses only feeNow()/(10000+feeNow()) of the input (33.3% vs 50% at launch).\ncontract ExactOutputFeeProof is Test {\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    uint160 constant P = 79228162514264337593543950336;\n\n    IPoolManager m;\n    Work w;\n    ProofIMD imd;\n    WorkLaunchHook h;\n    PoolSwapTest r;\n    PoolModifyLiquidityTest lr;\n    PoolKey k; // hooked pool\n    PoolKey ck; // identical pool without the hook, gives the pre-fee amounts\n\n    function setUp() public {\n        vm.warp(1_700_000_000);\n        m = IPoolManager(address(new PoolManager(address(this))));\n        w = new Work();\n        vm.etch(IMD, address(new ProofIMD()).code);\n        imd = ProofIMD(IMD);\n        imd.mint(address(this), 1e27);\n        bytes32 hash = keccak256(abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(m, address(w))));\n        bytes32 salt;\n        for (uint256 i; i < 2_000_000; ++i) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(p) & 0x3fff == 0x2044) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        h = new WorkLaunchHook{salt: salt}(m, address(w));\n        k = _key(address(h));\n        ck = _key(address(0));\n        r = new PoolSwapTest(m);\n        lr = new PoolModifyLiquidityTest(m);\n        w.approve(address(r), type(uint256).max);\n        imd.approve(address(r), type(uint256).max);\n        w.approve(address(lr), type(uint256).max);\n        imd.approve(address(lr), type(uint256).max);\n        m.initialize(k, P);\n        m.initialize(ck, P);\n        lr.modifyLiquidity(k, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, 0), \"\");\n        lr.modifyLiquidity(ck, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, 0), \"\");\n    }\n\n    function _key(address hooks) internal view returns (PoolKey memory) {\n        (address c0, address c1) = address(w) < IMD ? (address(w), IMD) : (IMD, address(w));\n        return PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(hooks));\n    }\n\n    function _swap(PoolKey memory key, bool z, int256 amt) internal returns (BalanceDelta) {\n        return r.swap(\n            key,\n            IPoolManager.SwapParams(z, amt, z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n    }\n\n    function testExactOutputBuyPaysSameEffectiveFeeAsExactInputBuy() public {\n        assertEq(h.feeNow(), 5000); // same block as initialize: launch rate\n        bool buyIsZeroForOne = Currency.unwrap(k.currency0) == IMD;\n\n        // Route A: exact input. Pay 100 IMD, receive WORK after the 50% output haircut.\n        BalanceDelta c = _swap(ck, buyIsZeroForOne, -100 ether);\n        BalanceDelta a = _swap(k, buyIsZeroForOne, -100 ether);\n        uint256 outCtl = uint256(int256(buyIsZeroForOne ? c.amount1() : c.amount0()));\n        uint256 outHook = uint256(int256(buyIsZeroForOne ? a.amount1() : a.amount0()));\n        uint256 effInBps = 1e4 - outHook * 1e4 / outCtl;\n\n        // Route B: exact output. Ask for exactly the WORK route A delivered; pay IMD plus the hook fee on the input.\n        BalanceDelta c2 = _swap(ck, buyIsZeroForOne, int256(outHook));\n        BalanceDelta b = _swap(k, buyIsZeroForOne, int256(outHook));\n        uint256 inCtl = uint256(-int256(buyIsZeroForOne ? c2.amount0() : c2.amount1()));\n        uint256 inHook = uint256(-int256(buyIsZeroForOne ? b.amount0() : b.amount1()));\n        uint256 effOutBps = 1e4 - inCtl * 1e4 / inHook;\n\n        emit log_named_uint(\"control pool WORK out for 100 IMD\", outCtl);\n        emit log_named_uint(\"control pool IMD in for that WORK\", inCtl);\n        emit log_named_uint(\"WORK bought with 100 IMD via exact-input\", outHook);\n        emit log_named_uint(\"IMD paid for that same WORK via exact-output\", inHook);\n        emit log_named_uint(\"effective fee bps, exact-input\", effInBps);\n        emit log_named_uint(\"effective fee bps, exact-output\", effOutBps);\n\n        // The launch guard is a 50% tax on a purchase. Both routes must lose the same fraction (1 bps tolerance).\n        assertApproxEqAbs(effOutBps, effInBps, 1, \"exact-output route pays a lower effective fee than exact-input\");\n    }\n}","reproduction":"Real PoolManager, IMD stand-in ERC-20 etched at 0x5F7B...7127, hook at a 0x2044 CREATE2 address, pool initialised at sqrtPrice 2^96 and funded with 1_000_000e18 liquidity in [-600,600]; an identical pool without the hook gives the pre-fee amounts. Same block as initialize, feeNow() == 5000. (A) exact-input buy, amountSpecified = -100e18 IMD: control pool pays out 98,740,249,400,371,713,293 WORK, hooked pool pays out 49,370,124,700,185,856,647 WORK -> effective fee 5000 bps. (B) exact-output buy of exactly 49,370,124,700,185,856,647 WORK: control pool takes 50,007,406,615,701,650,270 IMD, hooked pool takes 75,011,109,923,552,475,405 IMD -> effective fee 3334 bps. Expected: both routes lose the same 50%; actual: route B obtains the identical WORK for 75.0 IMD instead of 100 IMD. test/scratch/ExactOutputFee.t.sol fails on the current code with '3334 !~= 5000' and passes against a copy of the hook whose exact-output branch uses f = |a|*fee/(10000-fee).","severity":"medium","snippet":"        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Exact-output swaps pay a 33% effective launch fee instead of 50%: the fee is a fraction of the unspecified amount, so the two swap modes tax the same trade differently"},{"citation":"resolved","description":"beforeInitialize stamps openedAt and the fee slides from 50% to standingFee over the next 900 s regardless of whether the pool has any liquidity. README step 4 (deploy + initialize atomically) and step 5 (supply initial liquidity under the launch policy) are separate operations and the hook has no view of liquidity. If the factory or policy seeds liquidity in a later transaction, the launch tax the manifest promises ('50% at openedAt sliding to the standing fee') has partly or fully elapsed before the first trade is even possible, and the first buyers pay the standing 2%. Flow-gap seam execution x first principles: every step is correct in isolation, the end state contradicts the guard's purpose. Fix options: make the factory seed liquidity in the initialize transaction and state that as a hard launch requirement, or start the timer at the first afterAddLiquidity / first swap (which changes the permission flags and the fixed 0x2044 address and is therefore a design decision for the author).","line":42,"path":"src/WorkLaunchHook.sol","reproduction":"Real PoolManager; manager.initialize(key, 2^96) at T = 1,700,000,000 -> openedAt = T. No liquidity. vm.warp(T + 900); add 1_000_000e18 liquidity in [-600,600]; feeNow() == 200. First-ever swap, exact-input 100e18 IMD: trader receives 96,765,444,412,364,279,028 WORK and the hook mints 1,974,804,988,007,434,265 WORK claims = 2.00% of the pre-fee output. Expected for the first trade of a launch: the 50% guard; actual: 2%. test/scratch/Econ.t.sol::testGuardExpiresBeforeLiquidityArrives demonstrates the state.","severity":"low","snippet":"        openedAt = block.timestamp;","title":"Snipe-guard ramp is anchored to pool initialize, not to the first liquidity, so a seed that lands 900 s after initialize trades at the 2% standing fee from the first swap"},{"citation":"resolved","description":"unlockCallback redeems IMD claims then WORK claims in one callback, and sweep() then transfers the hook's raw IMD, WORK and native balances to B, all inside one transaction with no per-currency entry point and no try/catch. Any single leg reverting (take of IMD to B fails because the paired token blocks, pauses or blacklists the treasury; or the native call fails because the hook holds forced ETH and B is a contract without a payable receive) reverts the whole sweep, so the WORK fees, whose transfer can never fail, are stranded together with the failing currency until the external condition clears. B is a constant, so there is no alternative recipient. The author's REVIEW.md records the revert behaviour as accepted; this finding adds that the coupling turns a problem with one currency into loss of access to all accrued fees, and that the native leg is a 1-wei griefing vector (contract that selfdestructs into the hook in its creation transaction, still effective on Cancun) if the treasury is ever a non-payable contract. On 2026-10-08 neither 0xc9ea...12a0 nor the IMD address holds code on Sepolia (the README's default chain), Ethereum mainnet, Base, Arbitrum or Optimism, so today the treasury is an EOA and the IMD token's behaviour cannot be verified; the finding is a configuration constraint rather than a live loss. Fix: a per-currency sweep (sweep(Currency) or three independent functions) so a failing leg strands only its own currency; keep B fixed.","line":86,"path":"src/WorkLaunchHook.sol","reproduction":"Real PoolManager, pool initialised and funded, one exact-input swap in each direction so the hook holds both WORK and IMD claims (both > 0). IMD stand-in starts rejecting transfers to B (same effect as a blocklist). hook.sweep() reverts; afterwards manager.balanceOf(hook, WORK id) is unchanged and work.balanceOf(B) == 0 although nothing about WORK failed. Second trigger: vm.deal(hook, 1 wei) (or selfdestruct into it) with a contract at B whose receive() reverts: sweep() reverts and both claim balances stay in the PoolManager. test/scratch/Econ.t.sol::testOneBlockedLegStrandsAllFees and the author's testRevertingNativeTransferRollsBackClaimRedemption show both paths.","severity":"low","snippet":"            if (v > 0) {\n                poolManager.burn(address(this), x.toId(), v);\n                poolManager.take(x, B, v);\n            }","title":"sweep() is all-or-nothing across both claim currencies, both raw balances and native: one failing leg strands the fees of every currency, including WORK which never fails"},{"citation":"resolved","description":"The hook only constrains pools whose key names it as hooks. WORK is a plain ERC-20 with no transfer restriction and PoolManager.initialize is permissionless, so a WORK/IMD pool with hooks = address(0) and any fee tier can be created by anyone at any time, including during the 900 s launch window. Whoever holds WORK then (the factory's distribution recipients, or a buyer who paid the tax once) can provide liquidity there and every later trade routes around the guard and the standing fee: the treasury receives nothing from that volume and the snipe guard protects only the first hop. This is inherent to Uniswap v4 with an unrestricted token and is a trust assumption to document rather than a code defect; it is reported because the manifest notes state the guarantee without the qualifier. No fix inside the hook; mitigation is at the token (not wanted per brief) or in how the launch distributes WORK during the window.","line":39,"path":"src/WorkLaunchHook.sol","reproduction":"Real PoolManager; hooked pool initialised (openedAt set, feeNow() == 5000). Initialise PoolKey(WORK, IMD, fee 3000, tickSpacing 60, hooks address(0)) at the same price, add 1_000_000e18 liquidity, swap 100e18 IMD exact-input: trader receives 99,690,060,900,928,177,460 WORK; manager.balanceOf(hook, WORK id) == 0 and manager.balanceOf(hook, IMD id) == 0. Expected per the manifest notes: a 50% hook fee; actual: none. test/scratch/Econ.t.sol::testUnhookedPoolPaysNoFee.","severity":"info","snippet":"            openedAt == 0 && ((a == IMD && b == token) || (a == token && b == IMD)) && k.fee == 12500","title":"Only the single hooked pool is taxed: anyone can initialise a fee-free WORK/IMD pool on the same PoolManager, so 'every swap pays a hook fee' holds only for swaps routed through this pool"}],"hash":"3946453f52cf9419ddcfcd7c02aa17903f0169d3c6e44d6e6ef3ede9cdd08873","nodeId":"be5724e4-69ff-4ee3-ba8a-3d0a0a01df35","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. No source, config, or dependency file was changed. Only `test/scratch/` and the findings file were created, and the project's own 41 tests still pass.\n\n**Findings** (Economic Security, Invariant and Flow Gap guides, all reproduced against the real PoolManager):\n\n- **Medium, exact-output swaps are under-taxed.** The fee is a fraction of the unspecified amount, so an exact-input buy loses 50% of its output while an exact-output buy pays only 50% on top of its input, which is a 33.3% effective rate. The same WORK costs 100 IMD one way and 75 IMD the other. A sniper simply submits exact-output orders. The attached proof test fails on the current code and passes against a copy using `|a|·fee/(10000−fee)` on the exact-output branch. The brief froze the formula, so this needs the author's scope decision.\n- **Low, the ramp starts at initialize, not at liquidity.** A seed landing 900 s after initialize means the first-ever trade pays 2%, not 50%. README treats seeding as a separate step from initialization.\n- **Low, sweep is all-or-nothing.** If IMD ever blocks the treasury, or forced ETH meets a non-payable treasury contract, the WORK fees are stranded too. The author documented the revert; this adds the cross-currency coupling. Neither address holds code on Sepolia, mainnet, Base, Arbitrum or Optimism today, so this is a configuration constraint rather than a live loss.\n- **Info, only the hooked pool is taxed.** Anyone can open a fee-free WORK/IMD pool on the same manager. Inherent to v4 with an unrestricted token, reported because the manifest notes state the guarantee without that qualifier.\n\n**Coverage.** All eight entry points have rows, plus six invariant and lead rows. The token's three functions and `setStandingFee` hold. The one item I could not reach is verification of the paired token's transfer behaviour on the launch chain, recorded as unreached.\n\n**Rejected leads.** The admin lowering the fee mid-ramp only reduces what the treasury itself collects, with no unprivileged amplifier. Dust rounding to zero below 50 wei is not material. The Slither strict-equality and unused-return lines are not defects: `openedAt == 0` is the intended once-only guard and the unlock return value carries nothing.","treeHash":null,"usage":{"cachedInputTokens":1364393,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":39371,"runtime":"claude","turns":30,"wallClockMs":622031}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ef5038c1bdac3372","findings":[{"citation":"resolved","description":"sweep() is the only path that moves accrued fee claims out of the hook (unlockCallback is reachable only through sweep's own poolManager.unlock). After the claims are redeemed inside the unlock, the direct-balance loop at lines 74-79 iterates [IMD, token, address(0)] and calls Currency.transfer(B, v) for every non-zero balance; Currency.transfer reverts (NativeTransferFailed) if the native call to B fails, and the revert unwinds the claim redemption as well. The hook has no receive()/fallback(), so its native balance is normally zero, but on Cancun SELFDESTRUCT still forwards ether, so any unprivileged account can force 1 wei into the hook for a few thousand gas. The pool can never hold native currency (beforeInitialize at line 39 rejects any pair other than IMD/token), so the native leg has no legitimate purpose; it only adds an all-or-nothing dependency on B accepting ETH. Precondition: the fixed treasury 0xc9EAFE33...A12a0 is (or later becomes, e.g. via account abstraction code) a contract without a payable receive/fallback. The treasury's runtime on the launch chain was not verifiable in this offline review; if it is an EOA or a Safe the path is harmless. Minimal fix preserving behaviour: drop address(0) from the direct-balance sweep (or make the native leg a non-reverting best-effort send). Related: the same all-or-nothing structure means a treasury blocked by the IMD token would also freeze WORK fees, but that depends on IMD behaviour and was not reproduced.","line":78,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\ncontract PairStub is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\n/// Treasury runtime with no receive() and no fallback(): a plain contract wallet or module.\ncontract NonPayableTreasury {\n    function ping() external pure returns (uint256) { return 1; }\n}\n\n/// 1-wei force-send; on Cancun SELFDESTRUCT still transfers ether to the beneficiary.\ncontract ForceSend {\n    constructor(address payable target) payable { selfdestruct(target); }\n}\n\ncontract NativeBrickTest is Test {\n    address internal constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address internal constant B = 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0;\n    uint160 internal constant P = 79228162514264337593543950336;\n\n    IPoolManager manager;\n    Work work;\n    PairStub pair;\n    WorkLaunchHook hook;\n    PoolSwapTest router;\n    PoolModifyLiquidityTest lp;\n    PoolKey key;\n\n    function setUp() public {\n        vm.warp(1_700_000_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        work = new Work();\n        vm.etch(IMD, address(new PairStub()).code);\n        pair = PairStub(IMD);\n        pair.mint(address(this), 1e27);\n        vm.etch(B, address(new NonPayableTreasury()).code);\n        hook = _deploy(address(work));\n        (address c0, address c1) = address(work) < IMD ? (address(work), IMD) : (IMD, address(work));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(address(hook)));\n        router = new PoolSwapTest(manager);\n        lp = new PoolModifyLiquidityTest(manager);\n        work.approve(address(router), type(uint256).max);\n        pair.approve(address(router), type(uint256).max);\n        work.approve(address(lp), type(uint256).max);\n        pair.approve(address(lp), type(uint256).max);\n        manager.initialize(key, P);\n        lp.modifyLiquidity(\n            key,\n            IPoolManager.ModifyLiquidityParams({tickLower: -600, tickUpper: 600, liquidityDelta: 1e24, salt: 0}),\n            \"\"\n        );\n    }\n\n    function _deploy(address token) internal returns (WorkLaunchHook) {\n        bytes32 h = keccak256(abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(manager, token)));\n        for (uint256 i; i < 1_000_000; ++i) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if (uint160(p) & 0x3fff == 0x2044 && p.code.length == 0) {\n                return new WorkLaunchHook{salt: bytes32(i)}(manager, token);\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function testSweepSurvivesForcedNativeBalance() public {\n        // Fees accrue as claims.\n        router.swap(\n            key,\n            IPoolManager.SwapParams(true, -100 ether, TickMath.MIN_SQRT_PRICE + 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n        uint256 id0 = key.currency0.toId();\n        uint256 id1 = key.currency1.toId();\n        uint256 c0 = manager.balanceOf(address(hook), id0);\n        uint256 c1 = manager.balanceOf(address(hook), id1);\n        assertGt(c0 + c1, 0, \"fees accrued\");\n\n        // Unprivileged attacker force-sends 1 wei into the hook (it has no receive()).\n        address attacker = makeAddr(\"attacker\");\n        vm.deal(attacker, 1);\n        vm.prank(attacker);\n        new ForceSend{value: 1}(payable(address(hook)));\n        assertEq(address(hook).balance, 1);\n\n        // Expected: the permissionless sweep still delivers the ERC-20 fee claims to the treasury.\n        // Actual on current code: sweep() reverts in the native leg and every claim stays stuck.\n        hook.sweep();\n        assertEq(manager.balanceOf(address(hook), id0), 0, \"currency0 claims swept\");\n        assertEq(manager.balanceOf(address(hook), id1), 0, \"currency1 claims swept\");\n        assertEq(work.balanceOf(B) + pair.balanceOf(B), c0 + c1, \"treasury received the fees\");\n    }\n}","reproduction":"State: B's runtime is a contract with no receive()/fallback() (vm.etch(B, NonPayableTreasury.code)); pool initialised and funded; one swap of 100 IMD exact-in so the hook holds >0 ERC-6909 claims. Attacker: deploy `contract ForceSend { constructor(address payable t) payable { selfdestruct(t); } }` with value 1 wei targeting the hook address. Then anyone calls hook.sweep(). Expected: ERC-20 fee claims are burned and taken to B. Actual: sweep() reverts with WrappedError(B, 0x00000000, 0x, NativeTransferFailed 0xf4b3b1bc) from Currency.transfer at line 78; both claim balances remain in the hook and every later sweep() reverts identically (verified at +365 days), so all past and future fees are unrecoverable. The attached test fails on the current code at the hook.sweep() call and passes once the native leg no longer reverts.","severity":"low","snippet":"if (v > 0) x.transfer(B, v);","title":"sweep() native leg lets anyone brick fee withdrawal with a 1-wei force-send when the treasury cannot receive ETH"},{"citation":"resolved","description":"The fee is charged on the unspecified side, as the brief specifies, so an exact-input buyer loses 50% of the WORK output (effective price 2x) while an exact-output buyer pays 50% extra IMD on the input (effective price 1.5x). The snipe guard is therefore materially weaker for anyone who routes with exact-output. This matches the written spec ('fee on its unspecified side') and is recorded so the author can confirm the intended strength of the guard; it is not a code defect.","line":65,"path":"src/WorkLaunchHook.sol","reproduction":"Pool initialised at sqrtPriceX96 = 2^96 with 1e24 liquidity in [-600,600], block.timestamp == openedAt (fee 5000). Buy WORK with IMD exact-in 1e18 IMD: received 493749512422356483 WORK. Then exact-out for exactly 493749512422356483 WORK: paid 750001110938048527 IMD. Same WORK amount costs 1.0 IMD via exact-in and 0.75 IMD via exact-out (test/scratch/Probe.t.sol::testLaunchPremiumExactInVsExactOut, run locally).","severity":"info","snippet":"bool u1 = (p.amountSpecified < 0) == p.zeroForOne;","title":"Launch premium depends on swap mode: exact-output buys WORK 25% cheaper than exact-input during the 50% window"},{"citation":"resolved","description":"openedAt is set in beforeInitialize. If the factory initialises the pool but seeds liquidity in a later transaction (or liquidity is added more than 900 s after initialize for any operational reason), the 50% -> 2% ramp has already elapsed when the first tradeable liquidity appears, and the first buyers pay only the standing fee. The brief says 'after pool init', so this matches the spec; it is recorded as an operational precondition: initialization and seed liquidity must be atomic (or within the ramp) for the guard to have any effect.","line":42,"path":"src/WorkLaunchHook.sol","reproduction":"manager.initialize(key, 2^96) at T0; vm.warp(T0 + 900); add 1e24 liquidity in [-600,600]; hook.feeNow() == 200, so the very first swap against launch liquidity pays 2% instead of 50% (test/scratch/Probe.t.sol::testTimerStartsAtInitializeNotLiquidity).","severity":"info","snippet":"openedAt = block.timestamp;","title":"Snipe window runs from pool initialization, not from first liquidity"}],"hash":"3d4abf49c12bb3b31d814494e116b75c034bb17a8cc667b6ce4f3787d43bcd4b","nodeId":"49bb07b0-a119-4128-9d7d-f69b5e06e651","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json`, the source tree is unchanged, and only my scratch tests exist under `test/scratch/`.\n\n**Outcome: one low-severity defect, two informational notes, all eight entry points covered.** The hook's core control flow holds under the Execution Trace, Periphery and First Principles checklists.\n\n**What I verified**\n\n- **afterSwap** traced through the real v4-core 4.0.0 `Hooks.afterSwap` and `PoolManager.swap`. Unspecified-side selection is correct in all four swap modes. The hook's mint of `-f` and the manager's hook delta of `+f` net to zero, and the int128 cast is bounded by the 50% cap.\n- **beforeInitialize** enforces a single pool with the exact pair, fee tier, tick spacing and hook address, under the PoolManager-only modifier.\n- **unlockCallback** is reachable only through the hook's own unlock in `sweep()`. Burn and take use the same currency id, so no query-versus-operation divergence.\n- **setStandingFee** is bounded to 0 to 1000 and restricted to the treasury. Mid-ramp changes have no unprivileged amplifier, so I recorded them as a trust assumption rather than a finding.\n- **Periphery**: HookMiner, the Deploy script, the Currency library and the ERC6909 burn path. Flags 0x2044 match the constructor's permission struct and the manifest.\n- The existing 41-test suite passes offline.\n\n**Findings**\n\n1. **Low: forced-native brick of sweep().** The direct-balance loop at `src/WorkLaunchHook.sol:78` includes native currency, though the pool can never hold it. Anyone can selfdestruct 1 wei into the hook. If the treasury is a contract without a payable receive, every sweep reverts forever and all fee claims are stuck. Conditional on the treasury's runtime, which I could not verify offline. The attached proof fails on current code and passes once the native leg is removed.\n2. **Info: launch premium depends on swap mode.** Exact-output buys the same WORK for 0.75 IMD where exact-input costs 1.0 IMD during the 50% window. This follows the spec's unspecified-side rule, so it is recorded for confirmation only.\n3. **Info: snipe timer runs from initialize.** If seed liquidity lands 900 s or more after initialize, the first buyers pay the standing fee. Spec-conformant; recorded as an operational precondition.\n\n**Not reached**: the live treasury and IMD token runtimes on the launch chain. A treasury blocked by the IMD token would freeze WORK fees through the same all-or-nothing sweep, but that depends on unverified token behaviour and is noted inside finding 1 rather than reported separately.","treeHash":null,"usage":{"cachedInputTokens":1180476,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":33933,"runtime":"claude","turns":22,"wallClockMs":468790}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c65c96fe91d42eb7","findings":[],"hash":"3d5cd8c4732f903a6084201a450a9c675f6fced95b9331b66a925b79aacdba21","nodeId":"237c607c-74f9-4b0e-81e4-814226536634","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":908}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddc5542bddcad225","findings":[{"citation":"resolved","description":"Area: Math Precision / Numerical Gap (seam: precision x invariant). feeNow() is applied to |unspecified delta| in both swap modes. For an exact-input swap the unspecified side is the OUTPUT, so the trader keeps output*(1-r) and the hook takes r of the gross output. For an exact-output swap the unspecified side is the INPUT, and the hook's returned delta is ADDED to what the trader pays: the trader pays input*(1+r), so the fee is only r/(1+r) of the trader's spend. At the launch rate r=50% that is 33.3% instead of 50%; at r=2% it is 1.96%. The two modes buy the same WORK from the same pool state at prices that differ by 25%, so a sniper simply quotes exact-output and the 'snipe guard 50%' stated in launch.json notes and the brief is never collected in that mode. The treasury receives 25 IMD less per 100-IMD-equivalent buy. Arithmetic with the test numbers: exact-input 100 IMD -> pool output 98.740 WORK -> fee 49.370 -> trader 49.370 WORK (cost 2.026 IMD/WORK). Exact-output 49.370 WORK -> pool input 49.998 IMD -> fee 24.999 -> trader pays 74.996 IMD (cost 1.519 IMD/WORK). Fix options (a scope decision because it changes the fee rule): for exact-output swaps charge f = a*r/(10000-r) so that f/(a+f) = r, or document that the guard is r/(1+r) for exact-output. The proof passes with the first option (74.996 becomes ~100 IMD within 2%).","line":67,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\ncontract ProofPair is ERC20 {\n    constructor() ERC20(\"Test IMD\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// Exact-output swaps during the 50% launch guard pay only a third of the trader's spend as fee,\n/// while exact-input swaps pay half of the output. Same WORK received, 25% less IMD paid.\ncontract ProofExactOutput is Test {\n    address internal constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    uint160 internal constant PRICE = 79228162514264337593543950336;\n    uint256 internal constant T0 = 1_700_000_000;\n\n    IPoolManager manager;\n    Work work;\n    ProofPair pair;\n    WorkLaunchHook hook;\n    PoolSwapTest router;\n    PoolModifyLiquidityTest lp;\n    PoolKey key;\n    bool buyIsZeroForOne;\n\n    function setUp() public {\n        vm.warp(T0);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        work = new Work();\n        vm.etch(IMD, address(new ProofPair()).code);\n        pair = ProofPair(IMD);\n        pair.mint(address(this), 1e27);\n        hook = _deployHook();\n        (address c0, address c1) = address(work) < IMD ? (address(work), IMD) : (IMD, address(work));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(address(hook)));\n        buyIsZeroForOne = c0 == IMD;\n        router = new PoolSwapTest(manager);\n        lp = new PoolModifyLiquidityTest(manager);\n        work.approve(address(router), type(uint256).max);\n        pair.approve(address(router), type(uint256).max);\n        work.approve(address(lp), type(uint256).max);\n        pair.approve(address(lp), type(uint256).max);\n        manager.initialize(key, PRICE);\n        lp.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)), \"\");\n    }\n\n    function _deployHook() internal returns (WorkLaunchHook) {\n        bytes memory initCode = abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(manager, address(work)));\n        bytes32 h = keccak256(initCode);\n        for (uint256 salt; salt < 2_000_000; ++salt) {\n            address p =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), h)))));\n            if (uint160(p) & 0x3fff == 0x2044 && p.code.length == 0) {\n                WorkLaunchHook d = new WorkLaunchHook{salt: bytes32(salt)}(manager, address(work));\n                require(address(d) == p);\n                return d;\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function _swap(int256 amount) internal returns (BalanceDelta) {\n        bool z = buyIsZeroForOne;\n        return router.swap(\n            key,\n            IPoolManager.SwapParams(z, amount, z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n    }\n\n    function _imd(BalanceDelta d) internal view returns (int128) {\n        return buyIsZeroForOne ? d.amount0() : d.amount1();\n    }\n\n    function _work(BalanceDelta d) internal view returns (int128) {\n        return buyIsZeroForOne ? d.amount1() : d.amount0();\n    }\n\n    function testExactOutputBuyCostsSameAsExactInputBuyAtLaunch() public {\n        assertEq(hook.feeNow(), 5000);\n        uint256 snap = vm.snapshotState();\n\n        BalanceDelta dIn = _swap(-100 ether);\n        uint256 workOutExactIn = uint256(int256(_work(dIn)));\n        uint256 imdPaidExactIn = uint256(-int256(_imd(dIn)));\n        assertEq(imdPaidExactIn, 100 ether);\n        // ~49.37 WORK: pool gave ~98.74, hook kept 50% of the output.\n        assertLt(workOutExactIn, 50 ether);\n\n        vm.revertToState(snap);\n        BalanceDelta dOut = _swap(int256(workOutExactIn));\n        assertEq(uint256(int256(_work(dOut))), workOutExactIn);\n        uint256 imdPaidExactOut = uint256(-int256(_imd(dOut)));\n\n        // Same WORK received from the same pool state under the same 50% guard: the IMD cost must\n        // match within price-impact noise. Today exact-output pays ~74.996 IMD against 100 IMD.\n        assertApproxEqRel(imdPaidExactOut, imdPaidExactIn, 0.02e18, \"exact-output buy is 25% cheaper\");\n    }\n}","reproduction":"State: pool initialized at sqrtPriceX96=2^96 at time T0, 1e24 liquidity in [-600,600], block.timestamp == openedAt so feeNow()==5000. (1) PoolSwapTest.swap(key, {zeroForOne: IMD->WORK, amountSpecified: -100e18, limit}) -> trader pays 100e18 IMD, receives 49370124700185856647 WORK. (2) Revert to the same state and swap(key, {same direction, amountSpecified: +49370124700185856647}) -> trader receives the identical 49370124700185856647 WORK but pays only 74996297057832769509 IMD. Expected under a 50% launch fee: both buys cost about 100e18 IMD. Actual: exact-output costs 25.0% less; the hook claims 24.999e18 IMD instead of the ~49.37e18-equivalent it takes on the exact-input path.","severity":"medium","snippet":"        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Launch guard is one third weaker for exact-output swaps: fee rate applies to the pool-side unspecified amount, so 50% becomes 33.3% of what the trader actually pays"},{"citation":"resolved","description":"Area: Boundary (Step 4 sentinel-address branch, Step 2 corner case 'no payable receiver'). sweep() loops over [IMD, token, address(0)]; the address(0) branch does Currency.transfer, a raw call{value: v} to B that reverts on failure (v4-core Currency.sol NativeTransferFailed). The hook never produces native value, but anyone can force it onto the hook with SELFDESTRUCT in a constructor (still transfers balance under Cancun/EIP-6780) for 1 wei plus gas. If B is a contract without a payable receive/fallback (B is a hardcoded address whose nature on the launch chain is not verified in this tree), that single wei makes every future sweep() revert in the native branch AFTER the unlock callback has already burned and taken the claims, so the whole transaction unwinds. The claims stay in the PoolManager with no other withdrawal path, so all hook fee revenue is locked for good. REVIEW.md records the rollback behaviour but not that an unprivileged party can trigger it permanently for 1 wei. Assumption at the boundary: 'B accepts ETH'. Actual: a non-payable B plus 1 forced wei = permanent DoS of fee collection. Precondition: B rejects native value; if B is an EOA or a wallet with a payable fallback this is unreachable. Minimal fix that preserves intent: do not let the native branch block the token branches, e.g. attempt the native transfer with a low-level call and ignore failure, or move the native sweep into a separate function.","line":78,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\ncontract ProofPair is ERC20 {\n    constructor() ERC20(\"Test IMD\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// Treasury without a payable receive path (e.g. a contract wallet with no fallback).\ncontract Rejecter {\n    receive() external payable {\n        revert(\"no\");\n    }\n}\n\n/// Anyone can force native value onto the hook with SELFDESTRUCT in a constructor.\ncontract ForceSend {\n    constructor(address payable to) payable {\n        selfdestruct(to);\n    }\n}\n\n/// With a treasury that rejects native value, a 1-wei forced send makes sweep() revert forever,\n/// locking every IMD/WORK fee claim in the PoolManager.\ncontract ProofNativeBrick is Test {\n    address internal constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address internal constant B = 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0;\n    uint160 internal constant PRICE = 79228162514264337593543950336;\n    uint256 internal constant T0 = 1_700_000_000;\n\n    IPoolManager manager;\n    Work work;\n    ProofPair pair;\n    WorkLaunchHook hook;\n    PoolSwapTest router;\n    PoolModifyLiquidityTest lp;\n    PoolKey key;\n    bool buyIsZeroForOne;\n\n    function setUp() public {\n        vm.warp(T0);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        work = new Work();\n        vm.etch(IMD, address(new ProofPair()).code);\n        pair = ProofPair(IMD);\n        pair.mint(address(this), 1e27);\n        hook = _deployHook();\n        (address c0, address c1) = address(work) < IMD ? (address(work), IMD) : (IMD, address(work));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(address(hook)));\n        buyIsZeroForOne = c0 == IMD;\n        router = new PoolSwapTest(manager);\n        lp = new PoolModifyLiquidityTest(manager);\n        work.approve(address(router), type(uint256).max);\n        pair.approve(address(router), type(uint256).max);\n        work.approve(address(lp), type(uint256).max);\n        pair.approve(address(lp), type(uint256).max);\n        manager.initialize(key, PRICE);\n        lp.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)), \"\");\n        vm.etch(B, address(new Rejecter()).code);\n    }\n\n    function _deployHook() internal returns (WorkLaunchHook) {\n        bytes memory initCode = abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(manager, address(work)));\n        bytes32 h = keccak256(initCode);\n        for (uint256 salt; salt < 2_000_000; ++salt) {\n            address p =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), h)))));\n            if (uint160(p) & 0x3fff == 0x2044 && p.code.length == 0) {\n                WorkLaunchHook d = new WorkLaunchHook{salt: bytes32(salt)}(manager, address(work));\n                require(address(d) == p);\n                return d;\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function testSweepStillDeliversClaimsAfterForcedWei() public {\n        bool z = buyIsZeroForOne;\n        router.swap(\n            key,\n            IPoolManager.SwapParams(z, -100 ether, z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n        uint256 claims = manager.balanceOf(address(hook), key.currency0.toId())\n            + manager.balanceOf(address(hook), key.currency1.toId());\n        assertGt(claims, 0);\n\n        address griefer = address(0xBEEF);\n        vm.deal(griefer, 1);\n        vm.prank(griefer);\n        new ForceSend{value: 1}(payable(address(hook)));\n        assertEq(address(hook).balance, 1);\n\n        // Expected: the IMD/WORK fee claims still reach the treasury. Actual: sweep() reverts in the\n        // native branch of the direct-balance loop and the claims are locked permanently.\n        hook.sweep();\n        assertEq(manager.balanceOf(address(hook), key.currency0.toId()), 0);\n        assertEq(manager.balanceOf(address(hook), key.currency1.toId()), 0);\n        assertGt(pair.balanceOf(B) + work.balanceOf(B), 0);\n    }\n}","reproduction":"State: B has code whose receive() reverts (vm.etch a Rejecter at 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0); pool initialized and funded; one exact-input swap of 100e18 IMD so the hook holds >0 ERC-6909 claims. Then an unprivileged account with 1 wei deploys `new ForceSend{value: 1}(payable(hook))` whose constructor selfdestructs to the hook; address(hook).balance == 1. Call hook.sweep() from anyone. Expected: IMD/WORK claims are burned and taken to B (sweep succeeded in the same state before the forced wei). Actual: sweep() reverts with WrappedError(B, 0x00000000, Error('no'), NativeTransferFailed 0xf4b3b1bc) and the claim balances are unchanged; every later sweep() reverts the same way.","severity":"medium","snippet":"            if (v > 0) x.transfer(B, v);","title":"If the treasury cannot receive native value, one forced wei permanently bricks sweep() and locks all IMD/WORK fee claims"},{"citation":"resolved","description":"Area: Numerical Gap seam 3 (boundary x invariant). The invariant the brief states is 'the first 15 minutes of trading pay 50% sliding to 2%'. feeNow() measures elapsed time from openedAt, which beforeInitialize sets at pool initialization. Initialization creates no liquidity (README 'Deployment parameters' step 5 makes liquidity a separate step under the launch policy). If initial liquidity is added 900 s or more after initialize, or simply late in the window, the first tradable moment is already at the standing fee and the guard protects nothing. With liquidity at openedAt+900, the very first buy of 100 IMD receives 96.77 WORK (2% hook fee) instead of 49.37 WORK (50%). This is a flow/operational gap rather than an arithmetic error, reported because the ramp boundary is in the assigned math and no code guards the invariant. Fix is a scope decision: start the ramp at the first liquidity add (requires afterAddLiquidity permission, which changes flags) or make the factory seed liquidity in the same transaction as initialize and document that requirement as mandatory.","line":42,"path":"src/WorkLaunchHook.sol","reproduction":"State: manager.initialize(key, 2^96) at T0 (openedAt=T0). No liquidity. vm.warp(T0+900). Add 1e24 liquidity in [-600,600]. hook.feeNow() returns 200. First swap exact-input 100e18 IMD returns 96765444412364279028 WORK. Expected per the brief: the first trade after launch pays the 50% guard (about 49.37e18 WORK). Actual: 2% standing fee from the first possible trade.","severity":"low","snippet":"        openedAt = block.timestamp;","title":"Snipe-guard ramp is anchored to pool initialization, not to first liquidity, so the 50% guard can expire before anyone can trade"},{"citation":"resolved","description":"Area: Math Precision (wrong rounding direction for a fee; zero-rounding). f = |a| * feeNow() / 10000 truncates. At the standing 2% rate any swap whose unspecified amount is below 50 wei pays zero, and every swap leaks up to 1 wei to the trader. Splitting a swap into sub-50-wei pieces avoids the fee entirely, but each piece costs far more in gas than the fee avoided (18-decimal tokens, 1 wei = 1e-18), so there is no economic exploit and no compounding. Recorded for completeness of the assigned math pass; a mulDivUp would remove it.","line":67,"path":"src/WorkLaunchHook.sol","reproduction":"State: pool initialized at T0 and funded with 1e24 liquidity; vm.warp(T0+900) so feeNow()==200. Ten exact-input swaps of 50 wei IMD each return 48 wei WORK and the hook's claim balances on both currencies stay 0 (48*200/10000 = 0). One exact-input swap of 500 wei returns 483 wei WORK and the hook claims 9 wei (483*200/10000 = 9.66 -> 9). Expected under fee-rounds-up: 1 wei per 48-wei swap and 10 wei on the 500-wei swap. Actual: 0 and 9.","severity":"info","snippet":"        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Fee rounds down and truncates to zero for unspecified amounts below 10000/feeNow wei (dust only)"}],"hash":"4041a9925aac6fd4802e719a56a058fa895c7547bdf653ee36b90d67ea618b44","nodeId":"e0b5d72a-fde1-4230-ae56-d4bcdeac1a26","outcome":"completed","summary":"The review is complete. The findings file is in place and valid, the scratch tests are removed, and no tracked file was changed. The findings file shows as ignored by git, which matches how the other task artifacts under `.imd/` are handled.\n\n**Outcome:** four substantiated findings on the hook's math and boundaries, two of them medium with Foundry proofs that fail on the current code. The ramp arithmetic itself is sound: no overflow, underflow, or wrong-sign path exists for any admissible standing fee or elapsed time, and the hook's net delta after every swap is zero.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Medium, exact-output swaps weaken the snipe guard.** The rate is applied to the pool-side unspecified amount, which is added to what an exact-output trader pays. The effective fee is r/(1+r), so the 50% launch guard collects 33.3% of spend. The proof buys the same WORK both ways from the same state and shows 75 IMD versus 100 IMD.\n2. **Medium, forced wei can brick sweep if the treasury rejects ETH.** The native branch of the direct-balance loop reverts on failure after the claims are already redeemed, so the whole sweep unwinds. One wei sent via selfdestruct by anyone locks all fee claims permanently. This depends on the treasury address being a non-payable contract, which the tree does not establish either way.\n3. **Low, ramp anchored to initialize rather than first liquidity.** With liquidity seeded 900 s after initialize, the first possible trade already pays 2%.\n4. **Info, fee rounds down to zero for sub-50-wei unspecified amounts.** Dust only, no economic exploit.\n\n**Coverage:** all eight listed entry points have rows, plus two invariant rows. The int128 minimum negation in afterSwap is the only arithmetic revert and is unreachable at realistic balances. Static-analysis leads on strict equality, timestamp use, and the ignored unlock return were checked and are not defects.","treeHash":null,"usage":{"cachedInputTokens":1358063,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":37176,"runtime":"claude","turns":21,"wallClockMs":527295}},{"artifacts":[],"attempt":1,"bundleHash":"85e31a26ff9223879b443e04acbe4085822b531e2fbed9f5ac4a27e528fbc98f","device":"4ba44f3d5ab7b010","findings":[],"hash":"4cd5b5ff1e545f34f20bf7b5b909d979c346090b6e070069fcafafd15267c9df","nodeId":"3e39693e-eb0a-45df-9a55-904ea41a7c9d","outcome":"completed","summary":"Formatted `launch.json`, preserving all supplied values and the exact notes string. Only the manifest changed.\n\nValidated schema, constructor arguments, permissions, and pool settings. Offline build, all 41 tests, a second 1,024-run fuzz pass, formatting check, and deployment dry run passed.","treeHash":"68045e3213bc3020276125bfa7a443c0db3d0864","usage":{"cachedInputTokens":244992,"inputTokens":36089,"model":"gpt-6-astra","outputTokens":3699,"runtime":"codex","turns":4,"wallClockMs":140357}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fbcdfc017217af1f","findings":[{"citation":"resolved","description":"Merged from audit_math (medium), audit_economics (medium), audit_permissions (low) and audit_flow (info): one root cause. afterSwap charges f = |unspecified| * feeNow()/10000 in every swap mode and returns +f on the unspecified side. For an exact-input buy the unspecified side is the WORK output, so the trader keeps (1-r) of the pool output: effective rate r. For an exact-output buy the unspecified side is the IMD input, and Hooks.afterSwap subtracts the hook delta from the trader's delta, so the trader pays pool_input*(1+r): effective rate r/(1+r). At the launch rate r=50% that is 33.3%; at the 10% maximum standing fee 9.09%; at 2% 1.96%. Any router lets a sniper submit amountSpecified > 0, so during the 900 s window the same WORK is bought for 25% less IMD than the exact-input route, and the treasury collects one third less than the headline rate on every exact-output purchase. The code implements the brief's wording ('fee on its unspecified side'), so this is a defect in the stated guarantee ('snipe guard 50%') rather than a slip; fixing it changes the fee formula and needs the author's scope decision. Minimal fix that keeps 'fee on the unspecified side': when the unspecified delta is negative (trader pays it) charge f = |a| * fee / (10000 - fee) so both routes lose the fraction r. With that one-line change the attached proof passes (exact-output cost becomes 100.01 IMD, effective 5000 bps); verified locally against a patched copy of the hook.","line":67,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\n/// @dev Stand-in for the fixed IMD address; etched there in setUp.\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\n/// Exact-input and exact-output purchases of the same WORK amount must lose the same\n/// fraction to the hook. Today exact-input loses feeNow()/10000 of the output while\n/// exact-output loses only feeNow()/(10000+feeNow()) of the input (33.3% vs 50% at launch).\ncontract ExactOutputFeeProof is Test {\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    uint160 constant P = 79228162514264337593543950336;\n\n    IPoolManager m;\n    Work w;\n    ProofIMD imd;\n    WorkLaunchHook h;\n    PoolSwapTest r;\n    PoolModifyLiquidityTest lr;\n    PoolKey k; // hooked pool\n    PoolKey ck; // identical pool without the hook, gives the pre-fee amounts\n\n    function setUp() public {\n        vm.warp(1_700_000_000);\n        m = IPoolManager(address(new PoolManager(address(this))));\n        w = new Work();\n        vm.etch(IMD, address(new ProofIMD()).code);\n        imd = ProofIMD(IMD);\n        imd.mint(address(this), 1e27);\n        bytes32 hash = keccak256(abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(m, address(w))));\n        bytes32 salt;\n        for (uint256 i; i < 2_000_000; ++i) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(p) & 0x3fff == 0x2044) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        h = new WorkLaunchHook{salt: salt}(m, address(w));\n        k = _key(address(h));\n        ck = _key(address(0));\n        r = new PoolSwapTest(m);\n        lr = new PoolModifyLiquidityTest(m);\n        w.approve(address(r), type(uint256).max);\n        imd.approve(address(r), type(uint256).max);\n        w.approve(address(lr), type(uint256).max);\n        imd.approve(address(lr), type(uint256).max);\n        m.initialize(k, P);\n        m.initialize(ck, P);\n        lr.modifyLiquidity(k, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, 0), \"\");\n        lr.modifyLiquidity(ck, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, 0), \"\");\n    }\n\n    function _key(address hooks) internal view returns (PoolKey memory) {\n        (address c0, address c1) = address(w) < IMD ? (address(w), IMD) : (IMD, address(w));\n        return PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(hooks));\n    }\n\n    function _swap(PoolKey memory key, bool z, int256 amt) internal returns (BalanceDelta) {\n        return r.swap(\n            key,\n            IPoolManager.SwapParams(z, amt, z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n    }\n\n    function testExactOutputBuyPaysSameEffectiveFeeAsExactInputBuy() public {\n        assertEq(h.feeNow(), 5000); // same block as initialize: launch rate\n        bool buyIsZeroForOne = Currency.unwrap(k.currency0) == IMD;\n\n        // Route A: exact input. Pay 100 IMD, receive WORK after the 50% output haircut.\n        BalanceDelta c = _swap(ck, buyIsZeroForOne, -100 ether);\n        BalanceDelta a = _swap(k, buyIsZeroForOne, -100 ether);\n        uint256 outCtl = uint256(int256(buyIsZeroForOne ? c.amount1() : c.amount0()));\n        uint256 outHook = uint256(int256(buyIsZeroForOne ? a.amount1() : a.amount0()));\n        uint256 effInBps = 1e4 - outHook * 1e4 / outCtl;\n\n        // Route B: exact output. Ask for exactly the WORK route A delivered; pay IMD plus the hook fee on the input.\n        BalanceDelta c2 = _swap(ck, buyIsZeroForOne, int256(outHook));\n        BalanceDelta b = _swap(k, buyIsZeroForOne, int256(outHook));\n        uint256 inCtl = uint256(-int256(buyIsZeroForOne ? c2.amount0() : c2.amount1()));\n        uint256 inHook = uint256(-int256(buyIsZeroForOne ? b.amount0() : b.amount1()));\n        uint256 effOutBps = 1e4 - inCtl * 1e4 / inHook;\n\n        emit log_named_uint(\"control pool WORK out for 100 IMD\", outCtl);\n        emit log_named_uint(\"control pool IMD in for that WORK\", inCtl);\n        emit log_named_uint(\"WORK bought with 100 IMD via exact-input\", outHook);\n        emit log_named_uint(\"IMD paid for that same WORK via exact-output\", inHook);\n        emit log_named_uint(\"effective fee bps, exact-input\", effInBps);\n        emit log_named_uint(\"effective fee bps, exact-output\", effOutBps);\n\n        // The launch guard is a 50% tax on a purchase. Both routes must lose the same fraction (1 bps tolerance).\n        assertApproxEqAbs(effOutBps, effInBps, 1, \"exact-output route pays a lower effective fee than exact-input\");\n    }\n}","reproduction":"Real v4.0.0 PoolManager; IMD stand-in ERC-20 etched at 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127; hook at a 0x2044 CREATE2 address; hooked pool and an identical hookless control pool initialised at sqrtPriceX96 = 2^96 and funded with 1_000_000e18 liquidity in [-600,600]; same block as initialize so feeNow() == 5000. (A) exact-input buy, amountSpecified = -100e18 IMD: control pool pays 98740249400371713293 WORK, hooked pool pays 49370124700185856647 WORK -> effective fee 5000 bps. (B) exact-output buy of exactly 49370124700185856647 WORK: control pool takes 50007406615701650270 IMD, hooked pool takes 75011109923552475405 IMD -> effective fee 3334 bps. Expected: both routes lose 50%; actual: route B obtains the identical WORK for 75.0 IMD instead of ~100 IMD. Attached proof fails on the current code with '3334 !~= 5000' and passes with the fee/(10000-fee) exact-output branch.","severity":"medium","snippet":"        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Exact-output swaps pay only r/(1+r) of the launch fee: the 50% snipe guard is a 33% guard for any trader who quotes exact output"},{"citation":"resolved","description":"Merged from audit_flow (low), audit_permissions (low), audit_math (medium) and audit_economics (low): one root cause. sweep() is the only path that redeems the hook's ERC-6909 fee claims (unlockCallback is reachable only through sweep's own poolManager.unlock). After the claims are burned and taken to B inside the unlock, the direct-balance loop at lines 74-79 iterates [IMD, token, address(0)] and calls Currency.transfer(B, v) for every non-zero balance in the same transaction. The native branch is a raw call that reverts with NativeTransferFailed if B rejects it, and the revert unwinds the claim redemption too. The pool can never hold native currency (beforeInitialize accepts only IMD/WORK) and the hook has no receive(), so the native leg has no legitimate purpose; it only adds a dependency on B accepting ETH. Anyone can force 1 wei into the hook with selfdestruct in a constructor (still transfers value on Cancun under EIP-6780). Precondition: B must be a contract (or an EIP-7702-delegated EOA) without a payable receive/fallback. Verified with cast code on 2026-10-08: B = 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0 has no code on Sepolia (README default chain), Ethereum mainnet or Base, so the path is not live today; it becomes live the moment the treasury address holds non-payable code, and there is no admin, alternative recipient or per-currency sweep to recover. The same coupling means a paired-token (IMD) transfer failure to B strands the WORK fees as well (IMD's live behaviour is unverifiable: it has no code on those chains today). Minimal fix preserving the design: drop address(0) from the direct-balance loop, or make the native leg a non-reverting best-effort send, or give each currency its own sweep entry. With address(0) removed the attached proof passes; verified locally against a patched copy of the hook.","line":78,"path":"src/WorkLaunchHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {Work} from \"src/Work.sol\";\nimport {WorkLaunchHook} from \"src/WorkLaunchHook.sol\";\n\ncontract ProofPair is ERC20 {\n    constructor() ERC20(\"Test IMD\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// Treasury without a payable receive path (e.g. a contract wallet with no fallback).\ncontract Rejecter {\n    receive() external payable {\n        revert(\"no\");\n    }\n}\n\n/// Anyone can force native value onto the hook with SELFDESTRUCT in a constructor.\ncontract ForceSend {\n    constructor(address payable to) payable {\n        selfdestruct(to);\n    }\n}\n\n/// With a treasury that rejects native value, a 1-wei forced send makes sweep() revert forever,\n/// locking every IMD/WORK fee claim in the PoolManager.\ncontract ProofNativeBrick is Test {\n    address internal constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address internal constant B = 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0;\n    uint160 internal constant PRICE = 79228162514264337593543950336;\n    uint256 internal constant T0 = 1_700_000_000;\n\n    IPoolManager manager;\n    Work work;\n    ProofPair pair;\n    WorkLaunchHook hook;\n    PoolSwapTest router;\n    PoolModifyLiquidityTest lp;\n    PoolKey key;\n    bool buyIsZeroForOne;\n\n    function setUp() public {\n        vm.warp(T0);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        work = new Work();\n        vm.etch(IMD, address(new ProofPair()).code);\n        pair = ProofPair(IMD);\n        pair.mint(address(this), 1e27);\n        hook = _deployHook();\n        (address c0, address c1) = address(work) < IMD ? (address(work), IMD) : (IMD, address(work));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12500, 60, IHooks(address(hook)));\n        buyIsZeroForOne = c0 == IMD;\n        router = new PoolSwapTest(manager);\n        lp = new PoolModifyLiquidityTest(manager);\n        work.approve(address(router), type(uint256).max);\n        pair.approve(address(router), type(uint256).max);\n        work.approve(address(lp), type(uint256).max);\n        pair.approve(address(lp), type(uint256).max);\n        manager.initialize(key, PRICE);\n        lp.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)), \"\");\n        vm.etch(B, address(new Rejecter()).code);\n    }\n\n    function _deployHook() internal returns (WorkLaunchHook) {\n        bytes memory initCode = abi.encodePacked(type(WorkLaunchHook).creationCode, abi.encode(manager, address(work)));\n        bytes32 h = keccak256(initCode);\n        for (uint256 salt; salt < 2_000_000; ++salt) {\n            address p =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), h)))));\n            if (uint160(p) & 0x3fff == 0x2044 && p.code.length == 0) {\n                WorkLaunchHook d = new WorkLaunchHook{salt: bytes32(salt)}(manager, address(work));\n                require(address(d) == p);\n                return d;\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function testSweepStillDeliversClaimsAfterForcedWei() public {\n        bool z = buyIsZeroForOne;\n        router.swap(\n            key,\n            IPoolManager.SwapParams(z, -100 ether, z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n        uint256 claims = manager.balanceOf(address(hook), key.currency0.toId())\n            + manager.balanceOf(address(hook), key.currency1.toId());\n        assertGt(claims, 0);\n\n        address griefer = address(0xBEEF);\n        vm.deal(griefer, 1);\n        vm.prank(griefer);\n        new ForceSend{value: 1}(payable(address(hook)));\n        assertEq(address(hook).balance, 1);\n\n        // Expected: the IMD/WORK fee claims still reach the treasury. Actual: sweep() reverts in the\n        // native branch of the direct-balance loop and the claims are locked permanently.\n        hook.sweep();\n        assertEq(manager.balanceOf(address(hook), key.currency0.toId()), 0);\n        assertEq(manager.balanceOf(address(hook), key.currency1.toId()), 0);\n        assertGt(pair.balanceOf(B) + work.balanceOf(B), 0);\n    }\n}","reproduction":"State: pool initialised at 2^96 and funded with 1_000_000e18 liquidity; one exact-input swap of 100e18 IMD so the hook holds > 0 claims; vm.etch(B, Rejecter.code) where Rejecter's receive() reverts. Attacker 0xBEEF with 1 wei deploys `new ForceSend{value: 1}(payable(hook))` whose constructor selfdestructs to the hook; address(hook).balance == 1. Anyone calls hook.sweep(). Expected: IMD/WORK claims are burned and taken to B (sweep succeeds in the same state without the forced wei, test/scratch/Judge.t.sol::testSweepWorksWithEoaTreasury). Actual: sweep() reverts with WrappedError(B, 0x00000000, Error('no'), NativeTransferFailed 0xf4b3b1bc) from Currency.transfer at line 78; both claim balances are unchanged and every later sweep() reverts identically. Attached proof fails on the current code at the hook.sweep() call.","severity":"medium","snippet":"            if (v > 0) x.transfer(B, v);","title":"sweep() is all-or-nothing and includes a native leg the pool never uses: 1 forced wei plus a treasury that rejects ETH locks every accrued fee claim permanently"},{"citation":"resolved","description":"From audit_permissions (low), reproduced. The only caller check is onlyPM; the sender and sqrtPriceX96 arguments the PoolManager forwards are discarded. The accepted PoolKey (sorted IMD/WORK, fee 12500, tickSpacing 60, hooks = this) is public once the hook is deployed at its mined CREATE2 address, and PoolManager.initialize is permissionless. The first initializer wins: openedAt is set, the `openedAt == 0` guard rejects every later initialize, the pool sits at the attacker's price, and the 900 s ramp runs down before any liquidity exists. Nothing resets openedAt and the hook accepts exactly one pool, so the launch must be abandoned and the hook redeployed at a new address. Precondition: hook deployment and pool initialization are not performed atomically by the factory. The reference says the factory deploys the hook and initializes its pool and README item 4 requires atomicity, so with an atomic factory the window is zero; the hook itself provides no defence (the manifest offers $factory as a constructor argument, which would allow a sender check without changing the fee design, but that changes the supplied source and is the author's decision). Reported so the deployment constraint is explicit and verified rather than assumed.","line":39,"path":"src/WorkLaunchHook.sol","reproduction":"State: PoolManager, Work and WorkLaunchHook (0x2044 address) deployed, no pool. 1) vm.prank(0xA77); manager.initialize(key, TickMath.MIN_SQRT_PRICE + 1) -> succeeds; hook.openedAt() == block.timestamp; slot0.sqrtPriceX96 == MIN_SQRT_PRICE + 1. 2) factory calls manager.initialize(key, 79228162514264337593543950336) -> reverts (beforeInitialize require fails on openedAt != 0, wrapped as HookCallFailed). 3) vm.warp(+1000): hook.feeNow() == 200 with no liquidity ever added. Expected: only the factory opens the pool at the manifest price; actual: first-come initializer wins. test/scratch/Judge.t.sol::testThirdPartyInitializeLocksOutFactory passes (i.e. the behaviour is as described).","severity":"low","snippet":"            openedAt == 0 && ((a == IMD && b == token) || (a == token && b == IMD)) && k.fee == 12500","title":"beforeInitialize ignores sender and price: once the hook exists, any account can open the accepted pool at an arbitrary price, start the ramp, and lock the factory out for good"},{"citation":"resolved","description":"Merged from audit_math (low), audit_economics (low) and audit_flow (info). openedAt is stamped in beforeInitialize and feeNow() slides from 5000 to standingFee over the next 900 s regardless of whether the pool has liquidity. Initialization creates no liquidity and README step 5 makes seeding a separate step under the launch policy. If liquidity lands late in the window or after it, the first tradeable moment is already at (or near) the 2% standing fee and the guard protects nothing. This matches the brief's literal wording ('over 15 min after pool init') so it is a flow/operational gap, not an arithmetic error: the invariant 'the first 15 minutes of trading pay 50% sliding to 2%' is not enforced by code. Fix is a scope decision: require the factory to seed liquidity in the initialize transaction and state that as a hard launch requirement, or start the timer on first liquidity/first swap (changes permissions and the 0x2044 address).","line":42,"path":"src/WorkLaunchHook.sol","reproduction":"manager.initialize(key, 2^96) at T0 = 1_700_000_000 (openedAt = T0). No liquidity. vm.warp(T0 + 900). Add 1_000_000e18 liquidity in [-600,600]. hook.feeNow() == 200. First-ever swap, exact-input 100e18 IMD: trader receives 96765444412364279028 WORK (2% hook fee). Expected for the first trade of the launch: the 50% guard (about 49.37e18 WORK). test/scratch/Judge.t.sol::testRampExpiresBeforeLiquidity.","severity":"low","snippet":"        openedAt = block.timestamp;","title":"Snipe-guard ramp is anchored to pool initialization, not to first liquidity, so the 50% guard can elapse before anyone can trade"},{"citation":"resolved","description":"From audit_economics (info), reproduced. WORK is a plain ERC-20 and PoolManager.initialize is permissionless, so anyone holding WORK (distribution recipients, or a buyer who paid the tax once) can open a WORK/IMD pool with hooks = address(0) at any fee tier, including during the 900 s window, and all volume routed there pays the treasury nothing. Inherent to Uniswap v4 with an unrestricted token; a trust assumption to document (the manifest notes say 'every swap pays a hook fee' without the qualifier), not a code defect. No fix inside the hook.","line":39,"path":"src/WorkLaunchHook.sol","reproduction":"Hooked pool initialised and funded (feeNow() == 5000). Initialise PoolKey(sorted WORK/IMD, fee 3000, tickSpacing 60, hooks 0) at 2^96, add 1_000_000e18 liquidity, swap 100e18 IMD exact-input: trader receives 99690060900928177460 WORK; manager.balanceOf(hook, id) == 0 for both currencies. test/scratch/Judge.t.sol::testUnhookedPoolPaysNoFee.","severity":"info","snippet":"            openedAt == 0 && ((a == IMD && b == token) || (a == token && b == IMD)) && k.fee == 12500","title":"Only the single hooked pool is taxed: a hookless WORK/IMD pool on the same PoolManager routes around the guard and the standing fee"},{"citation":"resolved","description":"From audit_math (info), reproduced. f = |a| * feeNow() / 10000 truncates toward zero, so at the 2% standing rate any swap whose unspecified amount is below 50 wei pays no fee and every swap leaks up to 1 wei. Splitting into sub-50-wei swaps costs orders of magnitude more gas than the fee avoided with 18-decimal tokens, so there is no exploit. Recorded for completeness; a round-up would remove it.","line":67,"path":"src/WorkLaunchHook.sol","reproduction":"Pool initialised and funded; vm.warp(T0 + 900) so feeNow() == 200. Exact-input swap of 50 wei IMD returns 48 wei WORK and the hook's WORK claim balance stays 0 (48*200/10000 = 0). test/scratch/Judge.t.sol::testDustFeeRoundsToZero.","severity":"info","snippet":"        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Fee rounds down and truncates to zero below 10000/feeNow wei of unspecified amount (dust only, no economic exploit)"},{"citation":"resolved","description":"From audit_permissions (info), extended. The reference says manifest permissions must match getHookPermissions; the supplied source has no such getter. The permission set (beforeInitialize, afterSwap, afterSwapReturnDelta = 0x2000|0x40|0x4 = 0x2044) is enforced only by Hooks.validateHookPermissions in the constructor (Permissions positions 0, 7, 11), which reverts HookAddressNotValid at any other address. The manifest's three permissions match those positions and constructorArgs [$poolManager, $token] match the constructor order; the manifest validates against the schema (fee 12500 within range, tickSpacing 60, initialPrice 2^96 decimal, pairedCurrency lowercase). Separately, as of 2026-10-08 `cast code` returns 0x for both 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 (IMD) and 0xc9EAFE33A510a3a3d95A94c4f85AdaF6a3EA12a0 (treasury) on Sepolia (README default chain 11155111), Ethereum mainnet and Base. The pool cannot be funded or traded until IMD exists at that address, and the treasury is currently an EOA. README item 1 already asks the deployer to verify this; no code defect, the source was supplied verbatim by the requester.","line":23,"path":"src/WorkLaunchHook.sol","reproduction":"`forge inspect WorkLaunchHook abi` lists no getHookPermissions(). Deployed at a 0x2044 address: uint160(hook) & 0x3fff == 0x2044; at any other address the constructor reverts (existing test testWrongCreate2FlagsRevert). `cast code 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 --rpc-url https://ethereum-sepolia-rpc.publicnode.com` -> 0x; same for the treasury, and on mainnet/Base.","severity":"info","snippet":"        Hooks.validateHookPermissions(","title":"Service-evidence notes: hook exposes no getHookPermissions() (permissions verifiable only via the 0x2044 address check), and neither the paired IMD token nor the treasury holds code on the default lau"}],"hash":"4dfaaf01ff0372a9c917cf295e2e06e268fa001af4f14e1be5ffd19b0560a5ee","nodeId":"7f8a69c7-ab09-4da7-8ce4-881ba7c17d80","outcome":"completed","summary":"The findings file is written and the tree is otherwise untouched (both paths are excluded by the repository's own ignore rules, as intended).\n\n**Outcome.** The review is complete. `.imd-findings.json` holds 7 findings and 12 coverage rows. No critical or high defect exists; the two medium findings are the real ones and both carry a proof I ran.\n\n**What I kept, after reproducing each against the real v4.0.0 PoolManager:**\n\n- **Medium, exact-output asymmetry** (line 67). Four specialists reported this under different severities; it is one root cause and I merged it. An exact-output buyer pays r/(1+r), so the 50% guard is a 33% guard for anyone who quotes exact output. The identical WORK costs 75.0 IMD versus 100 IMD. The attached proof fails on the current code and passes against a patched copy that charges fee/(10000 - fee) on the input side.\n- **Medium, sweep native leg** (line 78). Four specialists reported this too, merged. One forced wei plus a treasury that rejects ETH locks every fee claim permanently, with no admin escape. I verified by RPC today that the treasury has no code on Sepolia, mainnet or Base, so the path is not live now. The attached proof fails here and passes once the native leg is removed.\n- **Low, permissionless initialize** (line 39). Any account can open the pool first at any price and lock the factory out. Only matters if deploy and initialize are not atomic.\n- **Low, ramp anchored to initialize** (line 42). Liquidity added at or after 900 seconds trades at 2% from the first swap.\n- **Info**: hookless-pool bypass, dust rounding to zero, and service-evidence notes. The hook has no getHookPermissions getter, and the IMD token has no code on the default launch chain today.\n\n**Dropped or downgraded.** The slither strict-equality and unused-return lines are false positives. The aderyn unsafe-ERC20 line is wrong: the v4 Currency library handles non-standard returns.\n\n**Coverage.** All 8 entry points answered: 5 hold, 3 carry findings. One extra row is unreached: whether 12500 is an admissible policy fee tier cannot be checked from this tree.\n\n**Verification.** The project's own 51 tests pass. My scratch tests under test/scratch reproduce every kept lead and are not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":1484651,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":30380,"runtime":"claude","turns":19,"wallClockMs":459543}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f768e94767a9dde3","findings":[{"citation":"resolved","description":"Access control (Pashov Access Control: 'unprotected initialization / deployment front-run'). The only caller check is onlyPM; the `sender` argument the PoolManager forwards (the account that called PoolManager.initialize) and the `sqrtPriceX96` argument are both discarded. Once the hook exists at its mined CREATE2 address, the accepted PoolKey (sorted IMD/WORK, fee 12500, tickSpacing 60, hooks=this) is fully public, so any account can call `PoolManager.initialize(key, anyPrice)`. The hook then records `openedAt` and, because of the `openedAt == 0` guard at line 39, refuses every later initialize: the factory's own initialize at the manifest price (2^96) reverts inside beforeInitialize (WrappedError/HookCallFailed), the pool sits at the attacker-chosen price, and the 900 s ramp runs down before any liquidity exists. Nothing can reset openedAt and the hook accepts exactly one pool, so the launch has to be abandoned and the hook redeployed at a new mined address. The manifest already offers `$factory` as a constructor argument, so the hook could have required `sender == factory` (or validated the price) with no change to the fee design. PRECONDITION: this only matters if hook deployment and pool initialization are not performed in the same transaction by the factory; the README (item 4) acknowledges the window and relies on the deployer to be atomic. With an atomic factory the window is zero, hence low. Also outside this area but same root: even an honest early initializer at the right price kills the snipe guard, since feeNow() is 200 bps after 900 s regardless of when liquidity arrives.","line":35,"path":"src/WorkLaunchHook.sol","reproduction":"State: PoolManager deployed, Work deployed, WorkLaunchHook deployed at a 0x2044 address, no pool yet. 1) attacker (any EOA) calls manager.initialize(key, TickMath.MIN_SQRT_PRICE + 1) -> succeeds; hook.openedAt() == block.timestamp; slot0.sqrtPriceX96 == MIN_SQRT_PRICE+1. 2) factory calls manager.initialize(key, 79228162514264337593543950336) -> reverts (hook beforeInitialize require: openedAt != 0). 3) warp +1000 s: hook.feeNow() == 200 although no liquidity was ever added. Expected: only the launch factory can open the pool, or the price is validated; actual: first-come initializer wins and the factory is locked out. Reproduced in test/scratch/Review.t.sol::test_thirdPartyInitializesPoolAndLocksOutFactory against the real v4.0.0 PoolManager.","severity":"low","snippet":"    function beforeInitialize(address, PoolKey calldata k, uint160) external onlyPM returns (bytes4) {","title":"beforeInitialize ignores the initializer: any address can open the accepted pool first, fixing the price and starting the snipe-guard timer, after which the factory is permanently locked out"},{"citation":"resolved","description":"Trust Gap seam economics x asymmetry (Pashov Trust Gap: 'formula whose result differs by input shape, exploitable by whoever picks the favorable side'). The fee is a fraction of the UNSPECIFIED side. For an exact-input buy the unspecified side is the WORK output, so at 50% the buyer receives half the WORK: effective price 2.0x pool price. For an exact-output buy the unspecified side is the IMD input, so the buyer pays 1.5x the IMD: effective price 1.5x. Same pool, same instant, same trader class, different cost purely by choosing amountSpecified sign. The documented intent is a '50% snipe guard'; the guard that actually binds is the weaker one (a 50% markup, not a 100% markup), because every sniper will use exact-output. At the 2% standing fee the gap is 1/0.98 = 1.0204 vs 1.02 (negligible), so this is a launch-window issue only. The code implements the written spec ('fee on its unspecified side'), so this is a design defect in the stated guarantee rather than an implementation slip; a symmetric guard would charge the fee on the output side for both modes (for exact-output, charge f on the specified WORK output via beforeSwap/afterSwap specified-delta) or scale the input-side rate to r/(1-r).","line":65,"path":"src/WorkLaunchHook.sol","reproduction":"State: pool initialized at 2^96 and funded with 1_000_000e18 liquidity on [-600,600], block.timestamp == openedAt (feeNow()==5000). Case A: swap exact-input 100e18 IMD -> trader receives 49.370e18 WORK (pool output 98.74e18, hook keeps 49.37e18). Case B (fresh state): swap exact-output for exactly 49.370e18 WORK -> trader pays 74.996e18 IMD (pool input 49.998e18, hook takes 24.999e18). Same WORK bought, 100.0 IMD vs 75.0 IMD. Expected: the launch-window cost of acquiring N WORK is independent of swap mode; actual: exact-output is 25% cheaper. Reproduced in test/scratch/Review.t.sol::test_exactOutputPaysLessSnipeFeeThanExactInput (logs both numbers).","severity":"low","snippet":"        bool u1 = (p.amountSpecified < 0) == p.zeroForOne;\n        int128 a = u1 ? d.amount1() : d.amount0();\n        uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;","title":"Snipe guard is asymmetric by swap mode: an exact-output buy at openedAt costs 1.5x, an exact-input buy costs 2x, so snipers choose exact-output and pay 25% less than the advertised 50% guard implies"},{"citation":"resolved","description":"Asymmetry (Pashov Asymmetry step 3: branch-symmetry, native vs ERC20) combined with access (sweep is permissionless, treasury fixed). The native branch at line 78 (`x.transfer(B, v)` for address(0)) uses CurrencyLibrary.transfer, which reverts with NativeTransferFailed if B rejects the call. Because that branch runs in the same transaction, after unlockCallback has already burned the claims and taken IMD/WORK to B, a revert rolls back the fee redemption too. The pool has no native currency, so the hook can only ever hold ETH if someone forces it in; anyone can do that with `new Boom{value:1}(hook)` where Boom's constructor does selfdestruct(hook) (EIP-6780 still transfers value when selfdestruct runs in the creation transaction). The hook has no other path to redeem claims, so every fee ever accrued becomes unreachable while B cannot accept ETH. PRECONDITION: B must be a contract that rejects plain ETH transfers (e.g. a contract without receive/fallback, or one whose receive reverts). I checked Sepolia (the README's default launch chain) with cast code: B = 0xc9EAFE33...12a0 has no code there today, i.e. it is an EOA and accepts ETH, so the DoS is not live on that chain. It becomes live if the treasury is later deployed as a non-payable contract at that address on the launch chain, or if the same hook is launched on a chain where that address holds such code. Fix preserving design: wrap the native leg in a try/skip (do not revert the claim redemption on native failure) or move the native forward into a separate function.","line":72,"path":"src/WorkLaunchHook.sol","reproduction":"State: pool initialized and funded; one swap of 10e18 (hook holds >0 ERC-6909 claims); vm.etch(B, 0x60006000fd) so B reverts on any call; `new Boom{value:1}(payable(hook))` -> hook.balance == 1. Call hook.sweep() from any account -> reverts (NativeTransferFailed inside sweep after unlockCallback succeeded); afterwards manager.balanceOf(hook, IMD id) and manager.balanceOf(hook, WORK id) are unchanged and B received nothing. Expected: the ERC20/claim redemption should not depend on a 1-wei native leg; actual: all fee redemption is blocked for as long as B rejects ETH. Reproduced in test/scratch/Review.t.sol::test_forcedWeiBlocksSweepWhenTreasuryRejectsEth.","severity":"low","snippet":"    function sweep() external {\n        poolManager.unlock(\"\");\n        address[3] memory c = [IMD, token, address(0)];","title":"sweep() redeems claims and forwards native balance in one atomic call: if the treasury cannot receive ETH, 1 wei forced into the hook (selfdestruct) permanently blocks redemption of all accrued fee cl"},{"citation":"resolved","description":"Outside my area (manifest/verification), recorded as a note. The reference states 'permissions must match getHookPermissions'. The supplied source exposes no getHookPermissions(); the permission set (beforeInitialize, afterSwap, afterSwapReturnDelta = flags 0x2000|0x40|0x4 = 0x2044) is enforced only by Hooks.validateHookPermissions in the constructor, which reverts HookAddressNotValid for a non-0x2044 address. I verified the Permissions struct positions (0, 7, 11) match the three manifest entries and that 0x2044 is the correct mask; the author's testWrongCreate2FlagsRevert covers the negative. If the verifier requires the getter it will not find one; if it checks address bits it is satisfied. No code defect; a service-evidence gap only. The source was supplied verbatim by the requester, so this is not a contributor error.","line":23,"path":"src/WorkLaunchHook.sol","reproduction":"cast abi (or `forge inspect WorkLaunchHook abi`) lists no getHookPermissions(). Constructor at a non-0x2044 CREATE2 address reverts with Hooks.HookAddressNotValid (existing test testWrongCreate2FlagsRevert). Deployed at a 0x2044 address: uint160(hook) & 0x3fff == 0x2044.","severity":"info","snippet":"        Hooks.validateHookPermissions(\n            IHooks(address(this)),\n            Hooks.Permissions(\n                true, false, false, false, false, false, false, true, false, false, false, true, false, false\n            )\n        );","title":"No getHookPermissions() in the hook ABI; manifest permissions can only be checked against the constructor's address-flag validation"}],"hash":"8aa0a0c3887098080f8c4420cf6c2df96f4f67b0d6f7652b846cbad1129dc319","nodeId":"52860d76-064e-45f4-ac84-2599eae5dd11","outcome":"completed","summary":"Review complete. `.imd-findings.json` holds 4 findings and 13 coverage rows (all 8 verifier entry points plus 5 invariants); every snippet was verified to match the tree byte-for-byte, the author's 41 tests still pass, and `test/scratch/` has been removed so no tracked file changed.\n\n## Summary of the Access Control / Trust Gap / Asymmetry review\n\nThe permission model is small and tight: `onlyPM` on all three callbacks, a single hard-coded admin (`B`) with a bounded setter, one-shot `openedAt`, and a permissionless `sweep()` that can only ever pay `B`. I found no unauthorized-access path, no privilege escalation, no weaker second writer for any storage slot, and no way for a sweeper or swapper to redirect fees. The three defects I could substantiate are all **low**, each reproduced against the real v4.0.0 `PoolManager`:\n\n1. **`beforeInitialize` ignores `sender` and price** (`src/WorkLaunchHook.sol:35`). Once the hook exists, anyone can open the accepted pool at any price and start the ramp timer; the factory's own initialize then reverts forever (the one-pool guard cannot reset). Only matters if deploy + initialize aren't atomic — the README relies on that, and `$factory` was available as a constructor arg to close it.\n2. **Snipe guard is asymmetric by swap mode** (`:65–67`). At `openedAt`, buying 49.37 WORK costs 100 IMD exact-input but 75 IMD exact-output, because the fee is always on the unspecified side. Snipers will simply use exact-output, so the real guard is a 1.5x markup, not 2x. The code matches the written spec, so this is a design-guarantee gap.\n3. **Native leg coupled to claim redemption in `sweep()`** (`:72–74`). One wei forced via `selfdestruct` plus a treasury that rejects ETH blocks all fee redemption. I checked Sepolia: `B` currently has no code (an EOA), so this is conditional on a future non-payable contract at that address.\n\nPlus one **info** note outside my area: there is no `getHookPermissions()` in the ABI, so the manifest's permission list can only be checked against the constructor's 0x2044 address-flag validation (which I verified is correct).\n\nReported as trust assumptions, not defects: `B` can raise the fee to 10% instantly, including mid-ramp — bounded, documented, and router slippage covers both swap modes, so no unprivileged amplifier exists.","treeHash":null,"usage":{"cachedInputTokens":908021,"inputTokens":28,"model":"claude-fable-5-1","outputTokens":25790,"runtime":"claude","turns":14,"wallClockMs":472759}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5fb4f77b3d119b1d","findings":[],"hash":"a18f459729d366e05426823551441c35fefd507f4410e711cc5ba661d8614d9b","nodeId":"3e39693e-eb0a-45df-9a55-904ea41a7c9d","outcome":"failed","summary":"the task produced no changes; the agent's last message was: `launch.json` already matches the required manifest, so no changes were needed. Notes remain intact.\n\nValidation passed against the supplied schema, constructor ABI, and declared hook permissions (`0x2044`).\n\n`forge build` passed; `forge test` passed all 47 tests. No tracked files changed.","treeHash":null,"usage":{"cachedInputTokens":106240,"inputTokens":30438,"model":"gpt-6-astra","outputTokens":2768,"runtime":"codex","turns":3,"wallClockMs":84228}},{"artifacts":[],"attempt":1,"bundleHash":"5924d33324a2cb2bb8c598749093a3ceeb8858a58f5804d94c2d22d4d4b2a21c","device":"e4a4ecf9fefd4a46","findings":[],"hash":"abcbfdfc52baebc0dc59eb72cc19f429b8807fb3b5c12319efe41b19a205278b","nodeId":"f4100132-a8eb-4f99-a629-53c1998693b9","outcome":"completed","summary":"Fixed both reproduced defects: exact-output fee calculation and forced-ETH sweep blocking.\n\nBoth supplied proofs and all 47 permanent tests pass. `forge build`, `forge test`, and `forge fmt --check` pass.\n\nRecorded all seven findings in [.imd-responses.json](/home/seat/.identitymd/work/0f143654-9763-4fe7-a0c4-aca032047b3b/f4100132-a8eb-4f99-a629-53c1998693b9/.imd-responses.json). Updated deployment requirements and limitations; `launch.json` remains unchanged.\n\nLaunch still requires atomic initialization/liquidity and verification of live IMD code, currently absent on Sepolia.","treeHash":"61fa84ebf76a87723acf9567243e4132efc98366","usage":{"cachedInputTokens":913792,"inputTokens":109733,"model":"gpt-6-astra","outputTokens":13879,"runtime":"codex","turns":5,"wallClockMs":479297}},{"artifacts":[],"attempt":1,"bundleHash":"0c36f7e4a7bc1743271ea68935a2a3480596aee886e20cf02f8e674b7bd1b96a","device":"2d5f86a85eed40fa","findings":[],"hash":"dbfabc91e487b8c4335431e160840684c3ae4a92d17a41f80911d53ae23b953d","nodeId":"237c607c-74f9-4b0e-81e4-814226536634","outcome":"completed","summary":"Added failure-path tests, fee arithmetic checks, and token/hook invariants covering 65,536 randomized calls.\n\n`forge build` and `forge test` pass: **55 passed, 0 failed**.\n\nOnly `test/` paths changed. Tests use the real PoolManager with a local IMD stand-in; live-chain behavior remains unverified. No new defects were confirmed.","treeHash":"50a8b9aa30fab21356b3b75938a220bde87f6b3f","usage":{"cachedInputTokens":1906432,"inputTokens":108975,"model":"gpt-6-astra","outputTokens":18643,"runtime":"codex","turns":7,"wallClockMs":667526}},{"artifacts":[],"attempt":1,"bundleHash":"c3cd29df268042788f94444a7887f3fdd121fef40fa7c74b6c739a7bc8ffc882","device":"868f4d64276861c0","findings":[],"hash":"e26241886bf3ae474d2d0aeb262f93c1cfc5aae8af364b3d1e6971a08df1787d","nodeId":"237c607c-74f9-4b0e-81e4-814226536634","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests against the real PoolManager.\n\n- All 51 tests pass across two seeds.\n- Final run: 2,048 cases per fuzz test and 16,384 invariant calls.\n- Offline build, formatting, and deployment smoke check pass.\n- No reproducible contract defect found.\n\nAll deliverable changes are under `test/`.","treeHash":"30152fa4371e6abd3c35679636c6b6763110fb4a","usage":{"cachedInputTokens":2757376,"inputTokens":103628,"model":"gpt-6-astra","outputTokens":22892,"runtime":"codex","turns":8,"wallClockMs":831899}},{"artifacts":[],"attempt":2,"bundleHash":"f476c12fe0ef8c9451ec1eeaf59a687558d71febe37c650beba397f351b6662b","device":"11df27658f46aa9d","findings":[],"hash":"e9b5b149e37cb8a40fd059baa03a4f1e6b2e65bacfb165335ba649656d0c8e94","nodeId":"3e39693e-eb0a-45df-9a55-904ea41a7c9d","outcome":"completed","summary":"Updated only `launch.json` with a formatting-only revision, preserving every value and the exact notes string.\n\nSchema, constructor ABI, and permission checks passed. `forge build` succeeded; `forge test` passed all 47 tests.","treeHash":"e7f8d1853c42b752658ca9e718b3c333643c3dee","usage":{"cachedInputTokens":211840,"inputTokens":30326,"model":"gpt-6-astra","outputTokens":3706,"runtime":"codex","turns":3,"wallClockMs":136955}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3f91b58cf7cd2d45","findings":[],"hash":"eb2ea0465f178e3f8f7dd757370459d0cd2ac159681199335d1fe35e9ee3cd53","nodeId":"7f8a69c7-ab09-4da7-8ce4-881ba7c17d80","outcome":"completed","summary":"Both reproducible findings from the previous round are fixed, and nothing new reproduces. The findings file holds an empty list and a coverage record for all eight entry points.\n\n**Settled findings**\n\n- **Exact-output fee asymmetry (medium, a4c64ff8): fixed.** Line 69 of the hook now divides by 10000 minus the rate when the trader pays the unspecified side. All six attached proofs pass on the revised code. I also measured the trader's effective fee against a hookless control pool in all four modes at launch, mid-ramp, the 2% standing fee and the 10% maximum. Every mode matches feeNow() within 1 bps, and sweep delivers the exact-output claims to the treasury.\n- **Forced-wei sweep brick (medium, a2921de0): fixed.** The native leg is gone from the direct-balance loop. The proofs that force 1 wei into the hook with a rejecting treasury now pass, and the author added a permanent test for it.\n- **Advisory items (third-party initialize, ramp anchored to init, hookless pool, dust rounding, no getHookPermissions): settled.** The author documented the atomic factory requirement and the other constraints in the README and added reproduction tests. Each one still reproduces, but each is the brief's stated design rather than a code defect, so I did not restate them as findings. The two the author disputed were info-level and their dispute is sound.\n\n**Verification run**\n\n- Author's full suite: 55 tests pass, including the new differential swap-mode tests and the invariant suites.\n- Static-analysis leads checked: the strict equality in feeNow is a sentinel on an unset timestamp, and the ignored unlock return value is an empty bytes; neither is a defect.\n- The notes string in launch.json is byte-identical to the brief; the file was only reformatted.\n\n**One scope note for the record.** The brief said to write the hook source exactly as given, and the author changed one line to apply my fix. That change is the requested remediation and the fee is still charged on the unspecified side, so I treat it as the author's accepted scope decision.\n\nMy scratch tests were removed, so the tree is clean apart from the findings file.","treeHash":null,"usage":{"cachedInputTokens":604769,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":11922,"runtime":"claude","turns":9,"wallClockMs":319482}}],"verification":[{"checks":[{"durationMs":18320,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 18.10s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:67:21\n   │\n67 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:44\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:51\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:73:9\n   │\n73 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:85:25\n   │\n85 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:17\n   │\n87 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:88:17\n   │\n88 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":170,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156396)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 256, μ: 99410, ~: 99753)\nLogs:\n  Bound result 221134313163660581999474422\n\n[PASS] testMetadataAndSupply() (gas: 49155)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80482)\n[PASS] testRejectsInsufficientBalance() (gas: 34932)\n[PASS] testRejectsZeroRecipient() (gas: 30403)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 67.91ms (7.65ms CPU time)\n\nRan 35 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112135)\n[PASS] testAllSwapModesAtLaunch() (gas: 2606420)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2610055)\n[PASS] testAllSwapModesMidRamp() (gas: 2611795)\n[PASS] testCannotInitializeTwice() (gas: 161693)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43637)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1215564)\n[PASS] testFeeScheduleBoundaries() (gas: 190528)\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 256, μ: 40561, ~: 40499)\nLogs:\n  Bound result 3410\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 256, μ: 169134, ~: 169245)\nLogs:\n  Bound result 1518\n  Bound result 654\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 256, μ: 1253765, ~: 1252507)\nLogs:\n  Bound result 706198339455240231227\n  Bound result 1800\n  Bound result 902\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 256, μ: 43353, ~: 43423)\nLogs:\n  Bound result 899\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 140985)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 91393354)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179736)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7437445)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1898721)\n[PASS] testRejectsNativePair() (gas: 60117)\n[PASS] testRejectsWrongFee() (gas: 57955)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 43881)\n[PASS] testRejectsWrongPair() (gas: 52280)\n[PASS] testRejectsWrongTickSpacing() (gas: 58498)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 106737)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6722)\n[PASS] testRevertingNativeTransferRollsBackClaimRedemption() (gas: 1431977)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474065)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 980813)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190635)\n[PASS] testSwapBeforeInitializationReverts() (gas: 73918)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267910)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42300)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 90907)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12503)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3009395)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837248)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 68.12ms (186.59ms CPU time)\n\nRan 2 test suites in 68.83ms (136.03ms CPU time): 41 tests passed, 0 failed, 0 skipped (41 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":96,\"REVIEW.md\":44,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":1,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":93,\"test/Work.t.sol\":66,\"test/WorkLaunchHook.t.sol\":532,\"test/helpers/PairToken.sol\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1413,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/WorkLaunchHook.sol:46: WorkLaunchHook.feeNow() (src/WorkLaunchHook.sol#46-52) uses a dangerous strict equality:\n[medium/medium] unused-return at src/WorkLaunchHook.sol:72: WorkLaunchHook.sweep() (src/WorkLaunchHook.sol#72-80) ignores return value by poolManager.unlock() (src/WorkLaunchHook.sol#73)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:82: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#82-92) has external calls inside a loop: v = poolManager.balanceOf(address(this),x.toId()) (src/WorkLaunchHook.sol#85)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:82: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#82-92) has external calls inside a loop: poolManager.burn(address(this),x.toId(),v) (src/WorkLaunchHook.sol#87)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:82: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#82-92) has external calls inside a loop: poolManager.take(x,B,v) (src/WorkLaunchHook.sol#88)\n[low/medium] timestamp at src/WorkLaunchHook.sol:60: WorkLaunchHook.afterSwap(address,PoolKey,IPoolManager.SwapParams,BalanceDelta,bytes) (src/WorkLaunchHook.sol#60-70) uses timestamp for comparisons\n[low/medium] timestamp at src/WorkLaunchHook.sol:46: WorkLaunchHook.feeNow() (src/WorkLaunchHook.sol#46-52) uses timestamp for comparisons\n[low/medium] timestamp at src/WorkLaunchHook.sol:35: WorkLaunchHook.beforeInitialize(address,PoolKey,uint160) (src/WorkLaunchHook.sol#35-44) uses timestamp for comparisons","passed":true},{"durationMs":271,"exitCode":0,"name":"aderyn","output":"[low] empty-require-revert at src/WorkLaunchHook.sol:20: Empty `require()` / `revert()` Statement (4 places)\n[low] large-numeric-literal at src/Work.sol:6: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/WorkLaunchHook.sol:49: Literal Instead of Constant (7 places)\n[low] state-change-without-event at src/WorkLaunchHook.sol:35: State Change Without Event\n[low] unchecked-return at src/WorkLaunchHook.sol:73: Unchecked Return\n[low] uninitialized-local-variable at src/WorkLaunchHook.sol:75: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/WorkLaunchHook.sol:78: Unsafe ERC20 Operation","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2ee3f6e64c71856fcf67f186a03337f8428af09e68f88440e30373ef5bcf3aa9","verifiedTreeHash":"3e84dbdf3c116a61872ead60adff0c4e6f3b1b47","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":15310,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 15.17s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:67:21\n   │\n67 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:44\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:51\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:73:9\n   │\n73 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:85:25\n   │\n85 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:17\n   │\n87 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:88:17\n   │\n88 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":154,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156396)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 256, μ: 99229, ~: 99741)\nLogs:\n  Bound result 11\n\n[PASS] testMetadataAndSupply() (gas: 49155)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80482)\n[PASS] testRejectsInsufficientBalance() (gas: 34932)\n[PASS] testRejectsZeroRecipient() (gas: 30403)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.77ms (8.21ms CPU time)\n\nRan 35 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112135)\n[PASS] testAllSwapModesAtLaunch() (gas: 2606420)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2610055)\n[PASS] testAllSwapModesMidRamp() (gas: 2611795)\n[PASS] testCannotInitializeTwice() (gas: 161693)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43637)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1215564)\n[PASS] testFeeScheduleBoundaries() (gas: 190528)\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 256, μ: 40544, ~: 40487)\nLogs:\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129638946\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 256, μ: 169256, ~: 169245)\nLogs:\n  Bound result 114\n  Bound result 998\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 256, μ: 1253885, ~: 1252753)\nLogs:\n  Bound result 10000\n  Bound result 1789\n  Bound result 598\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 256, μ: 43351, ~: 43423)\nLogs:\n  Bound result 513\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 140985)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 91393354)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179736)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7437445)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1898721)\n[PASS] testRejectsNativePair() (gas: 60117)\n[PASS] testRejectsWrongFee() (gas: 57955)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 43881)\n[PASS] testRejectsWrongPair() (gas: 52280)\n[PASS] testRejectsWrongTickSpacing() (gas: 58498)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 106737)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6722)\n[PASS] testRevertingNativeTransferRollsBackClaimRedemption() (gas: 1431977)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474065)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 980813)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190635)\n[PASS] testSwapBeforeInitializationReverts() (gas: 73918)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267910)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42300)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 90907)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12503)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3009395)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837248)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 71.42ms (193.58ms CPU time)\n\nRan 2 test suites in 72.13ms (79.19ms CPU time): 41 tests passed, 0 failed, 0 skipped (41 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":96,\"REVIEW.md\":44,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":28,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":93,\"test/Work.t.sol\":66,\"test/WorkLaunchHook.t.sol\":532,\"test/helpers/PairToken.sol\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4cd5b5ff1e545f34f20bf7b5b909d979c346090b6e070069fcafafd15267c9df","verifiedTreeHash":"68045e3213bc3020276125bfa7a443c0db3d0864","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":21695,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 21.50s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/PairToken.sol:35:9:\n   |\n35 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:21\n   │\n69 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * r / (a < 0 ? 10000 - r : 10000);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:44\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:51\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:75:9\n   │\n75 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:25\n   │\n87 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:89:17\n   │\n89 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:90:17\n   │\n90 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":184,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156396)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 256, μ: 99815, ~: 99813)\nLogs:\n  Bound result 100\n\n[PASS] testMetadataAndSupply() (gas: 49155)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80482)\n[PASS] testRejectsInsufficientBalance() (gas: 34932)\n[PASS] testRejectsZeroRecipient() (gas: 30403)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.41ms (7.93ms CPU time)\n\nRan 41 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112250)\n[PASS] testAllSwapModesAtLaunch() (gas: 2614556)\n[PASS] testAllSwapModesAtMaxStandingFee() (gas: 2625382)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2618191)\n[PASS] testAllSwapModesMidRamp() (gas: 2619953)\n[PASS] testCannotInitializeTwice() (gas: 161825)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43736)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1218026)\n[PASS] testFeeScheduleBoundaries() (gas: 190710)\n[PASS] testForcedWeiCannotBlockTokenClaimsOrDonations() (gas: 1719209)\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 256, μ: 40742, ~: 40722)\nLogs:\n  Bound result 8540740845369530961907246726318\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 256, μ: 169154, ~: 169360)\nLogs:\n  Bound result 546\n  Bound result 0\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 256, μ: 1255597, ~: 1255237)\nLogs:\n  Bound result 685240201769859983231\n  Bound result 490\n  Bound result 619\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 256, μ: 43488, ~: 43561)\nLogs:\n  Bound result 553\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 141216)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 33600347)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179852)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7446181)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1860231)\n[PASS] testRejectsNativePair() (gas: 60301)\n[PASS] testRejectsWrongFee() (gas: 58073)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 44016)\n[PASS] testRejectsWrongPair() (gas: 52420)\n[PASS] testRejectsWrongTickSpacing() (gas: 58682)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 106921)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6838)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474283)\n[PASS] testReviewExactInputDustRoundsToZero() (gas: 935813)\n[PASS] testReviewHooklessPoolPaysNoHookFee() (gas: 670387)\n[PASS] testReviewPermissionsAreAddressBitsWithoutGetter() (gas: 9148)\n[PASS] testReviewRampCanExpireBeforeFirstLiquidity() (gas: 1256126)\n[PASS] testReviewThirdPartyCanInitializeBeforeFactory() (gas: 176301)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 981182)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190751)\n[PASS] testSwapBeforeInitializationReverts() (gas: 74101)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267618)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42574)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 91130)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12706)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3019336)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837557)\nSuite result: ok. 41 passed; 0 failed; 0 skipped; finished in 82.32ms (182.18ms CPU time)\n\nRan 2 test suites in 83.02ms (89.73ms CPU time): 47 tests passed, 0 failed, 0 skipped (47 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":105,\"REVIEW.md\":54,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":1,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":95,\"test/Work.t.sol\":66,\"test/WorkLaunchHook.t.sol\":618,\"test/helpers/PairToken.sol\":37},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1421,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/WorkLaunchHook.sol:46: WorkLaunchHook.feeNow() (src/WorkLaunchHook.sol#46-52) uses a dangerous strict equality:\n[medium/medium] unused-return at src/WorkLaunchHook.sol:74: WorkLaunchHook.sweep() (src/WorkLaunchHook.sol#74-82) ignores return value by poolManager.unlock() (src/WorkLaunchHook.sol#75)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:84: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#84-94) has external calls inside a loop: poolManager.burn(address(this),x.toId(),v) (src/WorkLaunchHook.sol#89)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:84: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#84-94) has external calls inside a loop: poolManager.take(x,B,v) (src/WorkLaunchHook.sol#90)\n[low/medium] calls-loop at src/WorkLaunchHook.sol:84: WorkLaunchHook.unlockCallback(bytes) (src/WorkLaunchHook.sol#84-94) has external calls inside a loop: v = poolManager.balanceOf(address(this),x.toId()) (src/WorkLaunchHook.sol#87)\n[low/medium] timestamp at src/WorkLaunchHook.sol:60: WorkLaunchHook.afterSwap(address,PoolKey,IPoolManager.SwapParams,BalanceDelta,bytes) (src/WorkLaunchHook.sol#60-72) uses timestamp for comparisons\n[low/medium] timestamp at src/WorkLaunchHook.sol:46: WorkLaunchHook.feeNow() (src/WorkLaunchHook.sol#46-52) uses timestamp for comparisons\n[low/medium] timestamp at src/WorkLaunchHook.sol:35: WorkLaunchHook.beforeInitialize(address,PoolKey,uint160) (src/WorkLaunchHook.sol#35-44) uses timestamp for comparisons","passed":true},{"durationMs":333,"exitCode":0,"name":"aderyn","output":"[low] empty-require-revert at src/WorkLaunchHook.sol:20: Empty `require()` / `revert()` Statement (4 places)\n[low] large-numeric-literal at src/Work.sol:6: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/WorkLaunchHook.sol:49: Literal Instead of Constant (7 places)\n[low] state-change-without-event at src/WorkLaunchHook.sol:35: State Change Without Event\n[low] unchecked-return at src/WorkLaunchHook.sol:75: Unchecked Return\n[low] uninitialized-local-variable at src/WorkLaunchHook.sol:77: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/WorkLaunchHook.sol:80: Unsafe ERC20 Operation","passed":true},{"durationMs":9487,"exitCode":0,"name":"proof a4c64ff87e30","output":"Compiling 78 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.87s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/PairToken.sol:35:9:\n   |\n35 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\n\nRan 1 test for test/imd-proof-f72657b4/Proof_a4c64ff87e30.t.sol:ExactOutputFeeProof\n[PASS] testExactOutputBuyPaysSameEffectiveFeeAsExactInputBuy() (gas: 638699)\nLogs:\n  control pool WORK out for 100 IMD: 98740249400371713293\n  control pool IMD in for that WORK: 50007406615701650270\n  WORK bought with 100 IMD via exact-input: 49370124700185856647\n  IMD paid for that same WORK via exact-output: 100014813231403300540\n  effective fee bps, exact-input: 5000\n  effective fee bps, exact-output: 5000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.51ms (654.40µs CPU time)\n\nRan 1 test suite in 6.16ms (5.51ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":9016,"exitCode":0,"name":"proof a2921de0cfc6","output":"2026-10-08T05:33:56.228788Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-gYPYt4/repo/test/imd-proof-f72657b4/Proof_a4c64ff87e30.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.41s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/imd-proof-d3e510b0/Proof_a2921de0cfc6.t.sol:35:9:\n   |\n35 |         selfdestruct(to);\n   |         ^^^^^^^^^^^^\n\n\nRan 1 test for test/imd-proof-d3e510b0/Proof_a2921de0cfc6.t.sol:ProofNativeBrick\n[PASS] testSweepStillDeliversClaimsAfterForcedWei() (gas: 429495)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.57ms (325.63µs CPU time)\n\nRan 1 test suite in 6.04ms (5.57ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"abcbfdfc52baebc0dc59eb72cc19f429b8807fb3b5c12319efe41b19a205278b","verifiedTreeHash":"61fa84ebf76a87723acf9567243e4132efc98366","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":32656,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 32.49s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/PairToken.sol:35:9:\n   |\n35 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:21\n   │\n69 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * r / (a < 0 ? 10000 - r : 10000);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:44\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:51\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:75:9\n   │\n75 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:25\n   │\n87 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:89:17\n   │\n89 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:90:17\n   │\n90 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":11009,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156396)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 1000, μ: 99353, ~: 99753)\nLogs:\n  Bound result 206250652607130241053747796\n\n[PASS] testMetadataAndSupply() (gas: 49155)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80482)\n[PASS] testRejectsInsufficientBalance() (gas: 34932)\n[PASS] testRejectsZeroRecipient() (gas: 30403)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 132.81ms (133.19ms CPU time)\n\nRan 45 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112250)\n[PASS] testAllSwapModesAtLaunch() (gas: 2614622)\n[PASS] testAllSwapModesAtMaxStandingFee() (gas: 2625382)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2618279)\n[PASS] testAllSwapModesMidRamp() (gas: 2620041)\n[PASS] testCannotInitializeTwice() (gas: 161825)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43736)\n[PASS] testDonationFailureAfterUnlockRollsBackClaimPayout() (gas: 1398276)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1218048)\n[PASS] testFeeScheduleBoundaries() (gas: 190798)\n[PASS] testForcedWeiCannotBlockTokenClaimsOrDonations() (gas: 1719230)\n[PASS] testFuzzRampComplementaryTimesHaveLinearSum(uint256,uint256) (runs: 1000, μ: 156285, ~: 156517)\nLogs:\n  Bound result 843\n  Bound result 644\n\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 1000, μ: 40741, ~: 40675)\nLogs:\n  Bound result 11043848305368756019\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 1000, μ: 169336, ~: 169388)\nLogs:\n  Bound result 1641\n  Bound result 597\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 1000, μ: 1256678, ~: 1255418)\nLogs:\n  Bound result 4994\n  Bound result 1000\n  Bound result 6\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 1000, μ: 40487, ~: 40570)\nLogs:\n  Bound result 128\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 141304)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 33600347)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179874)\n[PASS] testNoLiquiditySwapCannotInventFeeClaims() (gas: 579474)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7446247)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1860231)\n[PASS] testRejectsNativePair() (gas: 60367)\n[PASS] testRejectsWrongFee() (gas: 58073)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 44016)\n[PASS] testRejectsWrongPair() (gas: 52420)\n[PASS] testRejectsWrongTickSpacing() (gas: 58748)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 107009)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6838)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474283)\n[PASS] testReviewExactInputDustRoundsToZero() (gas: 935901)\n[PASS] testReviewHooklessPoolPaysNoHookFee() (gas: 670388)\n[PASS] testReviewPermissionsAreAddressBitsWithoutGetter() (gas: 9148)\n[PASS] testReviewRampCanExpireBeforeFirstLiquidity() (gas: 1256192)\n[PASS] testReviewThirdPartyCanInitializeBeforeFactory() (gas: 176323)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 981182)\n[PASS] testSettlementRevertPreservesExistingFeesDonationsAndPoolState() (gas: 1692283)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190751)\n[PASS] testSwapBeforeInitializationReverts() (gas: 74167)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267618)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42596)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 91196)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12706)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3019336)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837557)\nSuite result: ok. 45 passed; 0 failed; 0 skipped; finished in 156.04ms (778.90ms CPU time)\n\nRan 2 tests for test/WorkInvariant.t.sol:WorkInvariantTest\n[PASS]\nWorkInvariantTest invariants:\n[PASS] invariant_allowancesMatchApprovalsAndSpending\n[PASS] invariant_fixedSupplyAndIndividualBalancesMatchLedger\n WorkInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭------------------+------------------------+-------+---------+----------╮\n| Contract         | Selector               | Calls | Reverts | Discards |\n+========================================================================+\n| WorkTokenHandler | approve                | 5504  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| WorkTokenHandler | rejectOverspend        | 5420  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| WorkTokenHandler | rejectZeroRecipient    | 5347  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| WorkTokenHandler | revokeAndRejectSpender | 5413  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| WorkTokenHandler | transfer               | 5440  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| WorkTokenHandler | transferFrom           | 5644  | 0       | 0        |\n╰------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 98667997950\n  Bound result 100\n  Bound result 100000000000000000000000000\n  Bound result 0\n  Bound result 115792089237316195423570985008687907853269984665640230706124250674579796308072\n  Bound result 333333333333333234665335385\n  Bound result 0\n  Bound result 0\n  Bound result 100000000000000000000000\n  Bound result 0\n  Bound result 100000000000000000000000\n  Bound result 161290613\n  Bound result 10735936213801491\n  Bound result 10\n  Bound result 50\n  Bound result 900\n  Bound result 228162514264337593543947571\n  Bound result 333333333344069170879136876\n  Bound result 7829\n  Bound result 115792089237316195423570985008687907853269984665640230706124239938742250510013\n  Bound result 7373\n  Bound result 304\n  Bound result 4149\n  Bound result 0\n  Bound result 3\n  Bound result 385754892\n  Bound result 333333333344069170879144244\n  Bound result 117300738\n  Bound result 1257\n  Bound result 899\n  Bound result 32207512637407954\n  Bound result 900\n  Bound result 0\n  Bound result 899\n  Bound result 10\n  Bound result 9177\n  Bound result 0\n  Bound result 115792089237316195423570985008687907853269984665640230706124250674579796329680\n  Bound result 0\n  Bound result 118486917583947092038402211\n  Bound result 115792089237316195423570985008687907853269984665640230706124293617929979508939\n  Bound result 115792089237316195423570985008687907853269984665640230706124250674579796324597\n  Bound result 0\n  Bound result 115792089237316195423570985008687907853269984665640230706124207731229613088761\n  Bound result 115792089237316195423570985008687907853269984665640230706124293617929979509808\n  Bound result 8260\n  Bound result 333333333333333333333317692\n  Bound result 205\n  Bound result 115792089237316195423570985008687907853269984665639897372790874397896279771194\n  Bound result 1000\n  Bound result 2101\n  Bound result 208\n  Bound result 32457478374921741818216492343511247502422802700144433760115446131035078253\n  Bound result 8945\n  Bound result 97\n  Bound result 3\n  Bound result 5000\n  Bound result 115792089237316195423570985008687907853269984665640230706124293617929979515144\n  Bound result 115792089237316195423570985008687907853269984665640230706124293617929979510917\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129636724\n  Bound result 1000000000000000000000000000\n  Bound result 115792089237316195423570985008687907853269984665639897372790874397896279776964\n  Bound result 115792089237316195423570985008687907853269984665639897372790874397896279777943\n  Bound result 9644\n  Bound result 1700000000\n  Bound result 181024368460282355015828904\n  Bound result 1217\n  Bound result 1237592982803079001346346269327845\n  Bound result 6825\n  Bound result 115792089237316195423570985008687907853269984665639897374862196088189055398707\n  Bound result 115792089237316195423570985008687907853269984665640426624698152938483115335570\n  Bound result 1\n  Bound result 7000000000000000000\n  Bound result 2513991061\n  Bound result 152308964830107625620301393\n  Bound result 27353\n  Bound result 115792089237316195423570985008687907853269984665640230706124218467067158920391\n  Bound result 3110\n  Bound result 0\n  Bound result 8900\n  Bound result 115792089237316195423570985008687907853269984665640411730492753900287509338871\n  Bound result 8388608\n  Bound result 31536000\n  Bound result 10000000000000000000000000\n  Bound result 2600\n  Bound result 137044957633075479635989581\n  Bound result 81\n  Bound result 98\n  Bound result 5187\n\n[PASS] testFullSupplyZeroSelfAndInfiniteAllowanceSequence() (gas: 880644)\nLogs:\n  Bound result 333333333333333333333333333\n  Bound result 333333333333333333333333334\n  Bound result 1000000000000000000000000000\n  Bound result 0\n  Bound result 1\n  Bound result 999999999999999999999999999\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n  Bound result 1000000000000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.48s (4.48s CPU time)\n\nRan 2 tests for test/WorkLaunchInvariant.t.sol:WorkLaunchInvariantTest\n[PASS]\nWorkLaunchInvariantTest invariants:\n[PASS] invariant_feesAndDonationsAreConservedAndBacked\n[PASS] invariant_launchAndSettlementRemainValid\n[PASS] invariant_supplyIsConservedAcrossAllHolders\n WorkLaunchInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-------------------+--------------------+-------+---------+----------╮\n| Contract          | Selector           | Calls | Reverts | Discards |\n+=====================================================================+\n| WorkLaunchHandler | advanceTime        | 3288  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | donate             | 3258  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | failedSweep        | 3238  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | liquidityRoundTrip | 3228  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | rejectCallback     | 3316  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | rejectClaimTheft   | 3308  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | rejectFeeChange    | 3305  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | setStandingFee     | 3312  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | swap               | 3290  | 0       | 0        |\n|-------------------+--------------------+-------+---------+----------|\n| WorkLaunchHandler | sweep              | 3225  | 0       | 0        |\n╰-------------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 7000000000000000000\n  Bound result 9000000000000000000\n  Bound result 11597\n  Bound result 1\n  Bound result 9\n  Bound result 1\n  Bound result 1\n  Bound result 29\n  Bound result 84735380131560990509\n  Bound result 965878980394944737036\n  Bound result 257\n  Bound result 1\n  Bound result 231\n  Bound result 32\n  Bound result 51\n  Bound result 598\n  Bound result 100000000000000000000\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n  Bound result 6\n  Bound result 5\n  Bound result 19\n  Bound result 119870310\n  Bound result 1\n  Bound result 821785676532444195266\n  Bound result 8260\n  Bound result 12\n  Bound result 990\n  Bound result 4130\n  Bound result 50\n  Bound result 10087\n  Bound result 613079084992194156950\n  Bound result 13499\n  Bound result 1222\n  Bound result 908697008749520657401\n  Bound result 4\n  Bound result 515\n  Bound result 1\n  Bound result 15971\n  Bound result 10\n  Bound result 329\n  Bound result 552\n  Bound result 291\n  Bound result 1\n  Bound result 1259\n  Bound result 514264337593543950336\n  Bound result 12500\n  Bound result 439\n  Bound result 1000000000000000000000\n  Bound result 48\n  Bound result 483\n  Bound result 7\n  Bound result 5\n  Bound result 999999999999999999998\n  Bound result 1555\n  Bound result 3000\n  Bound result 26\n  Bound result 450\n  Bound result 1000000000000000000000\n  Bound result 3\n  Bound result 1001\n  Bound result 15\n  Bound result 13823\n  Bound result 9872\n  Bound result 598\n  Bound result 1001\n  Bound result 1\n  Bound result 48\n  Bound result 25\n  Bound result 3758\n  Bound result 6\n  Bound result 1\n  Bound result 457584007913129639888\n  Bound result 8802\n  Bound result 54\n  Bound result 78211774911133120729\n  Bound result 971\n  Bound result 100\n  Bound result 7623\n  Bound result 6806\n  Bound result 5\n  Bound result 2228\n  Bound result 1667\n  Bound result 900\n  Bound result 10845781274778018\n  Bound result 851\n  Bound result 162999269216087595044\n  Bound result 78\n  Bound result 348956166673113377135\n  Bound result 34\n  Bound result 1\n  Bound result 1\n  Bound result 2543956432\n  Bound result 7333\n  Bound result 1\n\n[PASS] testHandlerExercisesEveryActionAndCanResumeAfterFailure() (gas: 2913840)\nLogs:\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 7000000000000000000\n  Bound result 9000000000000000000\n  Bound result 1\n  Bound result 1000\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n  Bound result 1\n  Bound result 1000000000000000000000\n  Bound result 0\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 120\n  Bound result 1\n  Bound result 1000\n  Bound result 1000000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 10.91s (10.90s CPU time)\n\nRan 4 test suites in 10.91s (15.68s CPU time): 55 tests passed, 0 failed, 0 skipped (55 total tests)\n","passed":true},{"durationMs":53,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":105,\"REVIEW.md\":54,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":1,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":95,\"test/README.md\":62,\"test/Work.t.sol\":67,\"test/WorkInvariant.t.sol\":155,\"test/WorkLaunchHook.t.sol\":693,\"test/WorkLaunchInvariant.t.sol\":126,\"test/helpers/PairToken.sol\":37,\"test/helpers/WorkLaunchHandler.sol\":248,\"test/helpers/WorkPoolFixture.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"dbfabc91e487b8c4335431e160840684c3ae4a92d17a41f80911d53ae23b953d","verifiedTreeHash":"50a8b9aa30fab21356b3b75938a220bde87f6b3f","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":35376,"exitCode":0,"name":"build","output":"Compiling 87 files with Solc 0.8.26\nSolc 0.8.26 finished in 35.17s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n   --> test/helpers/WorkSequenceHandler.sol:285:9:\n    |\n285 |         selfdestruct(recipient);\n    |         ^^^^^^^^^^^^\n\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:67:21\n   │\n67 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * feeNow() / 10000;\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:44\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:51\n   │\n69 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:73:9\n   │\n73 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:85:25\n   │\n85 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:17\n   │\n87 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:88:17\n   │\n88 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":7273,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/WorkAdversarial.t.sol:WorkAdversarialTest\n[PASS] testDonatedClaimsAreRedeemedBeforePoolInitialization() (gas: 1337908)\n[PASS] testFuzzClaimsSettlementPreservesTraderAndHookAccounting(bool,bool,uint256,uint256) (runs: 2048, μ: 2217944, ~: 2219712)\nLogs:\n  Bound result 1\n  Bound result 1256\n\n[PASS] testNoLiquidityProducesNoPhantomFeesInAnySwapMode() (gas: 695470)\n[PASS] testRemovingAllLiquidityCannotConsumeAccruedClaims() (gas: 1480445)\n[PASS] testThirdPartyCannotTransferOrRedeemHookClaims() (gas: 2024221)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 385.84ms (387.20ms CPU time)\n\nRan 10 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156484)\n[PASS] testFullSupplySelfTransferAndRoundTrip() (gas: 219910)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 2048, μ: 99572, ~: 99838)\nLogs:\n  Bound result 978150895700277254745991323\n\n[PASS] testInfiniteAllowanceAndRevocation() (gas: 186781)\n[PASS] testMetadataAndSupply() (gas: 49177)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80548)\n[PASS] testRejectsInsufficientBalance() (gas: 34951)\n[PASS] testRejectsZeroRecipient() (gas: 30425)\n[PASS] testTransferFromFailureRestoresAllowanceAndBalances() (gas: 112472)\n[PASS] testZeroTransferAndInvalidReceiverDoNotBurnSupply() (gas: 149358)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 396.31ms (398.07ms CPU time)\n\nRan 35 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112135)\n[PASS] testAllSwapModesAtLaunch() (gas: 2606420)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2610055)\n[PASS] testAllSwapModesMidRamp() (gas: 2611795)\n[PASS] testCannotInitializeTwice() (gas: 161693)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43637)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1215564)\n[PASS] testFeeScheduleBoundaries() (gas: 190528)\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 2048, μ: 40559, ~: 40505)\nLogs:\n  Bound result 2524172564532082507500634424142622545014782\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 2048, μ: 169127, ~: 169245)\nLogs:\n  Bound result 200\n  Bound result 0\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 2048, μ: 1253451, ~: 1252491)\nLogs:\n  Bound result 479974159215590261688\n  Bound result 44\n  Bound result 771\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 2048, μ: 40356, ~: 40432)\nLogs:\n  Bound result 259\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 140985)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 91393354)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179736)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7437445)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1898721)\n[PASS] testRejectsNativePair() (gas: 60117)\n[PASS] testRejectsWrongFee() (gas: 57955)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 43881)\n[PASS] testRejectsWrongPair() (gas: 52280)\n[PASS] testRejectsWrongTickSpacing() (gas: 58498)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 106737)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6722)\n[PASS] testRevertingNativeTransferRollsBackClaimRedemption() (gas: 1431977)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474065)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 980813)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190635)\n[PASS] testSwapBeforeInitializationReverts() (gas: 73918)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267910)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42300)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 90907)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12503)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3009395)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837248)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 415.97ms (905.65ms CPU time)\n\nRan 1 test for test/WorkLaunchInvariant.t.sol:WorkLaunchInvariantTest\n[PASS]\nWorkLaunchInvariantTest invariants:\n[PASS] invariant_FeesAndDonationsAreConservedAndFullyBacked\n[PASS] invariant_FixedSupplyAndPoolStateRemainConsistent\n WorkLaunchInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+---------------+-------+---------+----------╮\n| Contract            | Selector      | Calls | Reverts | Discards |\n+==================================================================+\n| WorkSequenceHandler | advanceTime   | 1411  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | donate        | 1549  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | donateNative  | 1532  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | rejectedAdmin | 1528  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | rejectedSwap  | 1409  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | rejectedSweep | 1542  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | roundTrip     | 1478  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | setFee        | 1528  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | swap          | 1500  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | sweep         | 1453  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| WorkSequenceHandler | transferWork  | 1454  | 0       | 0        |\n╰---------------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000\n  Bound result 10000\n  Bound result 10000\n  Bound result 10000\n  Bound result 8675\n  Bound result 71\n  Bound result 5707\n  Bound result 920\n  Bound result 999999999999999989\n  Bound result 16193\n  Bound result 137\n  Bound result 520202123206284018015\n  Bound result 10000\n  Bound result 16895\n  Bound result 200\n  Bound result 12130\n  Bound result 78772174518346803434427394609596\n  Bound result 10\n  Bound result 16383\n  Bound result 899\n  Bound result 452809226\n  Bound result 11445\n  Bound result 115929652989304290\n  Bound result 5195\n  Bound result 200\n  Bound result 21\n  Bound result 300\n  Bound result 0\n  Bound result 100\n  Bound result 0\n  Bound result 503\n  Bound result 430\n  Bound result 16559\n  Bound result 405748779922909345777\n  Bound result 84972202369232953711\n  Bound result 922\n  Bound result 100\n  Bound result 999999999999999988\n  Bound result 10\n  Bound result 79\n  Bound result 999999999999999999001\n  Bound result 990\n  Bound result 245\n  Bound result 60\n  Bound result 10000\n  Bound result 10000\n  Bound result 797033440860445448\n  Bound result 6104\n  Bound result 900\n  Bound result 1000000000000000000\n  Bound result 32339272132406998523\n  Bound result 856362728185364027671\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.17s (7.16s CPU time)\n\nRan 4 test suites in 7.17s (8.37s CPU time): 51 tests passed, 0 failed, 0 skipped (51 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":96,\"REVIEW.md\":44,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":1,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":93,\"test/README.md\":78,\"test/Work.t.sol\":126,\"test/WorkAdversarial.t.sol\":161,\"test/WorkLaunchHook.t.sol\":464,\"test/WorkLaunchInvariant.t.sol\":122,\"test/helpers/PairToken.sol\":30,\"test/helpers/WorkPoolFixture.sol\":89,\"test/helpers/WorkSequenceHandler.sol\":287},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e26241886bf3ae474d2d0aeb262f93c1cfc5aae8af364b3d1e6971a08df1787d","verifiedTreeHash":"30152fa4371e6abd3c35679636c6b6763110fb4a","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":16979,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 16.84s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/PairToken.sol:35:9:\n   |\n35 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\nwarning[missing-events-arithmetic]: `openedAt` is changed without an event but is used in arithmetic\n   ╭▸ src/WorkLaunchHook.sol:42:9\n   │\n42 │         openedAt = block.timestamp;\n   │         ━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:49:23\n   │\n49 │         if (t == 0 || block.timestamp <= t) return 5000;\n   │                       ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/WorkLaunchHook.sol:51:16\n   │\n51 │         return t < 900 ? s + (5000 - s) * (900 - t) / 900 : s;\n   │                ━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:69:21\n   │\n69 │         uint256 f = uint256(int256(a < 0 ? -a : a)) * r / (a < 0 ? 10000 - r : 10000);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:44\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\n   │                                            ━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/WorkLaunchHook.sol:71:51\n   │\n71 │         return (IHooks.afterSwap.selector, int128(int256(f)));\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://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/WorkLaunchHook.sol:75:9\n   │\n75 │         poolManager.unlock(\"\");\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:87:25\n   │\n87 │             uint256 v = poolManager.balanceOf(address(this), x.toId());\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:89:17\n   │\n89 │                 poolManager.burn(address(this), x.toId(), v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/WorkLaunchHook.sol:90:17\n   │\n90 │                 poolManager.take(x, B, v);\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\n","passed":true},{"durationMs":137,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Work.t.sol:WorkTest\n[PASS] testAllowanceTransferAndExhaustion() (gas: 156396)\n[PASS] testFuzzTransferPreservesSupply(uint256) (runs: 256, μ: 99412, ~: 99753)\nLogs:\n  Bound result 41898539530277630664353180\n\n[PASS] testMetadataAndSupply() (gas: 49155)\n[PASS] testNoExternalMintOrAdminSurface() (gas: 80482)\n[PASS] testRejectsInsufficientBalance() (gas: 34932)\n[PASS] testRejectsZeroRecipient() (gas: 30403)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.56ms (5.99ms CPU time)\n\nRan 41 tests for test/WorkLaunchHook.t.sol:WorkLaunchHookTest\n[PASS] testAdminFeeBoundsAndEvent() (gas: 112250)\n[PASS] testAllSwapModesAtLaunch() (gas: 2614556)\n[PASS] testAllSwapModesAtMaxStandingFee() (gas: 2625382)\n[PASS] testAllSwapModesAtStandingFee() (gas: 2618191)\n[PASS] testAllSwapModesMidRamp() (gas: 2619953)\n[PASS] testCannotInitializeTwice() (gas: 161825)\n[PASS] testCreate2FlagsAndImmutables() (gas: 43736)\n[PASS] testDustFeeRoundsDownToZero() (gas: 1218026)\n[PASS] testFeeScheduleBoundaries() (gas: 190710)\n[PASS] testForcedWeiCannotBlockTokenClaimsOrDonations() (gas: 1719209)\n[PASS] testFuzzRejectsOutOfRangeFee(uint256) (runs: 256, μ: 40746, ~: 40701)\nLogs:\n  Bound result 72224813207765763006555208019502142490222915048453747477554959946810332\n\n[PASS] testFuzzScheduleIsBoundedAndMonotone(uint256,uint256) (runs: 256, μ: 169202, ~: 169360)\nLogs:\n  Bound result 1572\n  Bound result 252\n\n[PASS] testFuzzSwapAccounting(bool,bool,uint256,uint256,uint256) (runs: 256, μ: 1256572, ~: 1255158)\nLogs:\n  Bound result 357120520460486956371\n  Bound result 1182\n  Bound result 762\n\n[PASS] testFuzzUnauthorizedAdmin(address,uint256) (runs: 256, μ: 43495, ~: 43561)\nLogs:\n  Bound result 1000\n\n[PASS] testInitializeRecordsTimestampAndPrice() (gas: 141216)\n[PASS] testInitializeWorksWithBothTokenOrderings() (gas: 33600347)\n[PASS] testInvalidInitialPriceRollsBackOpenedAt() (gas: 179852)\n[PASS] testPartialFillsChargeOnlyExecutedUnspecifiedAmount() (gas: 7446181)\n[PASS] testPermissionlessSweepConservesBothCurrenciesAndDonations() (gas: 1860231)\n[PASS] testRejectsNativePair() (gas: 60301)\n[PASS] testRejectsWrongFee() (gas: 58073)\n[PASS] testRejectsWrongHookInAuthenticatedCallback() (gas: 44016)\n[PASS] testRejectsWrongPair() (gas: 52420)\n[PASS] testRejectsWrongTickSpacing() (gas: 58682)\n[PASS] testRejectsZeroAndDynamicFee() (gas: 106921)\n[PASS] testRejectsZeroTokenAndIMDAsToken() (gas: 6838)\n[PASS] testRevertingTokenTransferPreservesClaims() (gas: 1474283)\n[PASS] testReviewExactInputDustRoundsToZero() (gas: 935813)\n[PASS] testReviewHooklessPoolPaysNoHookFee() (gas: 670387)\n[PASS] testReviewPermissionsAreAddressBitsWithoutGetter() (gas: 9148)\n[PASS] testReviewRampCanExpireBeforeFirstLiquidity() (gas: 1256126)\n[PASS] testReviewThirdPartyCanInitializeBeforeFactory() (gas: 176301)\n[PASS] testSettlementFailureRollsBackSwapAndFeeClaims() (gas: 981182)\n[PASS] testStandingFeeChangeDuringAndAfterRamp() (gas: 190751)\n[PASS] testSwapBeforeInitializationReverts() (gas: 74101)\n[PASS] testSweepBeforeInitializationAndWithNoFees() (gas: 267618)\n[PASS] testSweepCannotNestInsideManagerUnlock() (gas: 42574)\n[PASS] testUnauthorizedCallbacksRevert() (gas: 91130)\n[PASS] testWrongCreate2FlagsRevert() (gas: 12706)\n[PASS] testZeroStandingFeeStillChargesDuringLaunch() (gas: 3019336)\n[PASS] testZeroSwapRevertsWithoutClaims() (gas: 837557)\nSuite result: ok. 41 passed; 0 failed; 0 skipped; finished in 57.91ms (169.51ms CPU time)\n\nRan 2 test suites in 58.70ms (63.47ms CPU time): 47 tests passed, 0 failed, 0 skipped (47 total tests)\n","passed":true},{"durationMs":30,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Work.approve(address,uint256)\",\"Work.transfer(address,uint256)\",\"Work.transferFrom(address,address,uint256)\",\"WorkLaunchHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"WorkLaunchHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"WorkLaunchHook.setStandingFee(uint256)\",\"WorkLaunchHook.sweep()\",\"WorkLaunchHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":105,\"REVIEW.md\":54,\"docs/dependencies.json\":34,\"foundry.toml\":19,\"launch.json\":21,\"remappings.txt\":4,\"script/Deploy.s.sol\":47,\"script/HookMiner.sol\":32,\"src/Work.sol\":8,\"src/WorkLaunchHook.sol\":95,\"test/Work.t.sol\":66,\"test/WorkLaunchHook.t.sol\":618,\"test/helpers/PairToken.sol\":37},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e9b5b149e37cb8a40fd059baa03a4f1e6b2e65bacfb165335ba649656d0c8e94","verifiedTreeHash":"e7f8d1853c42b752658ca9e718b3c333643c3dee","verifierVersion":"0.1.0+ad90ce4c"}]}