{"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":"e16fbe31-a5c8-46af-a344-9a04ec62b450","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"7380e7477977585ff6dc68ae370d70259276f580866a08178162949c59817cbe","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":"7eeed81c20c2a0915a9363748fb88f1edf3ba846a9afb5cd741f43c8590f93cd","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":"d2157ddcd00e51c766277bd4499ea0c9ba5514082e2ed934c472a57a691e1ef8","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":"61f397be5d638b16e9c5141c2dcf496c75ef8fee8d6ec6893fcf520d1a1d6253","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":"6a037cf2b87f32fc5e15bedc3238c1d7a95ef1e76d63c00866caab870737eb2f","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":"0a01891f47293647bf8fe8603ae3e837c2ae63a6c4dfabfa5cf39e784a38fc83","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":"84f29bbfbfa459a0eb021c32f35fdc280becb75a06fb61a7d4238ab6abf155be","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"8b2c324cc800667580f72b8c8f6932f473cdfb37486ff664e078fd58e977b56a","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":"A custom token: Workers (WORK).\nToken name: Workers\nToken symbol: WORK\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\nWhat it does: Trade fee 2%","parentJobId":null,"planHash":"76e8fa1f77c6e7b67f45c1a5fed3d4d5d133464d9b184268c8fa2fdd1b1dc058","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e16fbe31-a5c8-46af-a344-9a04ec62b450","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1000-workers"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51231","feedbackHash":"449d8d9bd848f5da8487950148ce0ba3e5f214ab2d0f6319f6780a63d7a8d941","nodeKey":"audit_economics","submissionHash":"7380e7477977585ff6dc68ae370d70259276f580866a08178162949c59817cbe","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51043","feedbackHash":"b51a2be9b038682ce87e570d4d4507f731e29b309bc105ca7eaaf6003b4de809","nodeKey":"audit_flow","submissionHash":"7eeed81c20c2a0915a9363748fb88f1edf3ba846a9afb5cd741f43c8590f93cd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52159","feedbackHash":"1500b62f06ddc5ffa8a5de238ab706df89030c8f37d2e12e14df72b4c88c37b8","nodeKey":"audit_judge","submissionHash":"235cce285c71c4c6c80fdf17e9b8308cda1107edc156fd50e756c95b1784f7bc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52165","feedbackHash":"eebd0b5abc0da91feec920ff800ce9dd6d3919ee9d443f683b12cf96bb90a675","nodeKey":"audit_judge","submissionHash":"d2157ddcd00e51c766277bd4499ea0c9ba5514082e2ed934c472a57a691e1ef8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51473","feedbackHash":"ede3a2dde0e053097fae8d3dc272b638d0e6d5d6488d16963228e3f137572412","nodeKey":"audit_math","submissionHash":"61f397be5d638b16e9c5141c2dcf496c75ef8fee8d6ec6893fcf520d1a1d6253","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51130","feedbackHash":"ff8240e16e924b84b85fd9fb540a1579c05b85ab030661923578c3aeecd76da0","nodeKey":"audit_permissions","submissionHash":"6a037cf2b87f32fc5e15bedc3238c1d7a95ef1e76d63c00866caab870737eb2f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51479","feedbackHash":"cb1ec135c683aaf2f5f07f1b757f70b14f02340cbda0ab1c6d67485dbfe14d57","nodeKey":"build_contract_project","submissionHash":"0a01891f47293647bf8fe8603ae3e837c2ae63a6c4dfabfa5cf39e784a38fc83","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51614","feedbackHash":"48632d2934b21978961ee77e122e2d12790d018bc15a7ccea21f8f1055407803","nodeKey":"build_contract_project","submissionHash":"f05fc461121492e28c45f23d36ad7f7121d89f33343d25187b524496f4b5b909","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51755","feedbackHash":"0c14aa115b04d68e03a01adfbf7e9441023e697a6b348bf8eb20eb5e3dbc2662","nodeKey":"manifest","submissionHash":"4401d3272058ac44cddf58715437018f05fa8e15db881821c87acb1a4c8c4584","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51522","feedbackHash":"d036a20596505291deda3ce3842958edfdd46e39db8b91f5d41df98d2fb55ac9","nodeKey":"manifest","submissionHash":"84f29bbfbfa459a0eb021c32f35fdc280becb75a06fb61a7d4238ab6abf155be","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51306","feedbackHash":"8b752ea321d9cacf6015bdd7ca115e1d9c5c035f159409fffe42ed1a0f472816","nodeKey":"write_foundry_tests","submissionHash":"8b2c324cc800667580f72b8c8f6932f473cdfb37486ff664e078fd58e977b56a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52145","feedbackHash":"df9a40079f426d18699de84d455275bdaeb32e9bf20d0386f77a555975a02b30","nodeKey":"write_foundry_tests","submissionHash":"1b6b54c92464d9d624f58aba904cf20badddadcfb95e4bae795d8173764eece1","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"740c0bdd2429ccb6bcfda50850e0f799a8753964042449338b7b9f5a4af38c46","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"54fd753868950a35a7cfe3005737f4d4b16e7d2c41597703d07b68d83eef927e","device":"49abac41ee211059","findings":[],"hash":"0a01891f47293647bf8fe8603ae3e837c2ae63a6c4dfabfa5cf39e784a38fc83","nodeId":"eb6bfc84-3928-4dc3-a08f-c3fb894bd902","outcome":"completed","summary":"Fixed the relay bypass, unsafe owner/fee-recipient settings, and dependency provenance. Added regression tests and answered all five findings in `.imd-responses.json`.\n\n`forge build`, `forge test` (63 passing, including the supplied proof), `forge fmt --check`, and all dependency checksums pass.\n\nLaunch-pool trades remain untaxed; that required settlement limitation is documented.","treeHash":"b15efa1f22cf1fdab1ebc60fb13c9cf4fd97bf63","usage":{"cachedInputTokens":737280,"inputTokens":68655,"model":"gpt-6-astra","outputTokens":12575,"runtime":"codex","turns":6,"wallClockMs":351411}},{"artifacts":[],"attempt":1,"bundleHash":"48e68f297804aca0aa006501f4c91b10f046363205713804a8e5c2b5eed29d61","device":"e02bec63aa9dcaa5","findings":[],"hash":"1b6b54c92464d9d624f58aba904cf20badddadcfb95e4bae795d8173764eece1","nodeId":"112277a7-0557-4473-a3fc-9ab7596a7826","outcome":"completed","summary":"Added 16 tests covering failure paths, fee rounding, allowance revocation, ownership changes, and stateful invariants across 16,384 random calls.\n\n`forge build --offline` and `forge test --offline` pass: **56 tests, 0 failures**.\n\nOnly two files under `test/` were added. No confirmed defects found.","treeHash":"82417d4f9cb7ca7145fca54e9ead9c742f76666d","usage":{"cachedInputTokens":1124480,"inputTokens":90182,"model":"gpt-6-astra","outputTokens":11705,"runtime":"codex","turns":6,"wallClockMs":379333}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6b37e4ab65246705","findings":[{"citation":"resolved","description":"_update exempts any transfer whose caller, sender or receiver is the launch PoolManager, and evaluates that before the venue flags. The Uniswap v4 PoolManager is a public singleton: anyone may call unlock(), and inside the callback sync(WORK), transfer WORK in, settle() to be credited exactly what arrived, and take(WORK, anyAddress, amount), which makes the manager itself transfer the tokens out (msg.sender == from == poolManager). No pool, hook or liquidity is required for these primitives. So a sell into a registered venue V can be routed user -> PoolManager (exempt: to == poolManager) -> V (exempt: msg.sender == poolManager), and a buy from V can be routed V -> PoolManager (exempt) -> buyer (exempt). Neither leg pays the 2% fee and no TradeFeePaid is emitted, whereas the direct user <-> V transfer pays 2 WORK per 100. The only cost is the gas of one unlock, so any trader, aggregator or public router can wrap every venue trade this way; the same clause also leaves every other v4 pool for WORK untaxed. The fee recipient loses the whole fee on routed volume; no holder loses principal, so medium (broken guarantee). The three poolManager clauses are only effective when a venue IS involved: when neither side is a venue the last clause already exempts the transfer. Minimal fix that keeps every launch flow exact (factory seed, trader <-> manager swaps, distributor transfers never involve a registered venue, and the factory, manager and distributor cannot be registered as venues): drop `msg.sender == poolManager || from == poolManager || to == poolManager` from the exemption so the condition reads `from == address(0) || to == address(0) || msg.sender == launchFactory || (!isTradeVenue[from] && !isTradeVenue[to])`, keeping the distributor branch. Verified on a copy: the attached proof then passes (3/3) and the launch flows in the protected harness involve no venue. Tradeoff for the author: README line 37 and tests test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive, test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount and test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers encode manager<->venue transfers arriving whole and must change with the fix. If the requester instead keeps the current precedence, the README must say the venue fee is avoidable by anyone via the PoolManager.","line":100,"path":"src/Workers.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Workers} from \"src/Workers.sol\";\n\n/// @dev Stands in for the launch factory: deploys the token (constructor requires msg.sender == factory with code)\n/// and answers distributorOf(uint64) the way the factory does.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address manager, uint64 number, address owner) external returns (Workers) {\n        return new Workers(address(this), manager, number, owner);\n    }\n\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev Minimal copy of the Uniswap v4 PoolManager currency-settlement primitives any unlocker may use:\n/// unlock() calls back the caller; sync() records reserves; settle() credits balance - reserves to the caller;\n/// take() transfers credited tokens out to any address. No pool, hook or key is needed for these.\ncontract PoolManagerStub {\n    Workers public token;\n    uint256 private reserves;\n    mapping(address => uint256) public credit;\n\n    function setToken(Workers token_) external {\n        token = token_;\n    }\n\n    function unlock(bytes calldata data) external returns (bytes memory) {\n        return IUnlockCallback(msg.sender).unlockCallback(data);\n    }\n\n    function sync() external {\n        reserves = token.balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        paid = token.balanceOf(address(this)) - reserves;\n        reserves = token.balanceOf(address(this));\n        credit[msg.sender] += paid;\n    }\n\n    function take(address to, uint256 amount) external {\n        credit[msg.sender] -= amount;\n        token.transfer(to, amount); // msg.sender == poolManager, from == poolManager\n        reserves = token.balanceOf(address(this));\n    }\n}\n\ninterface IUnlockCallback {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @dev A registered trading venue, e.g. a Uniswap v2 pair: it holds tokens and sends them wherever the swap's\n/// `to` says (pair.swap(amountOut, 0, to, \"\")). Modelled by a public send.\ncontract VenueStub {\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev Anyone can deploy this. It moves WORK between a user and a registered venue through the PoolManager's\n/// sync/settle/take so that no leg of the transfer is taxable.\ncontract FeeFreeRouter is IUnlockCallback {\n    Workers immutable token;\n    PoolManagerStub immutable manager;\n\n    constructor(Workers token_, PoolManagerStub manager_) {\n        token = token_;\n        manager = manager_;\n    }\n\n    /// Sell: user -> PoolManager (exempt: to == poolManager) -> venue (exempt: msg.sender/from == poolManager).\n    function sellToVenue(VenueStub venue, uint256 amount) external {\n        manager.unlock(abi.encode(true, msg.sender, address(venue), amount));\n    }\n\n    /// Buy: venue -> PoolManager (exempt: to == poolManager) -> user (exempt: msg.sender/from == poolManager).\n    function buyFromVenue(VenueStub venue, uint256 amount) external {\n        manager.unlock(abi.encode(false, msg.sender, address(venue), amount));\n    }\n\n    function unlockCallback(bytes calldata data) external override returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (bool sell, address user, address venue, uint256 amount) = abi.decode(data, (bool, address, address, uint256));\n        manager.sync();\n        if (sell) token.transferFrom(user, address(manager), amount);\n        else VenueStub(venue).send(token, address(manager), amount);\n        uint256 paid = manager.settle();\n        manager.take(sell ? venue : user, paid);\n        return \"\";\n    }\n}\n\ncontract PoolManagerRelayBypassTest is Test {\n    uint64 constant LAUNCH_NUMBER = 41;\n\n    FactoryStub factory;\n    PoolManagerStub manager;\n    VenueStub venue;\n    Workers token;\n    FeeFreeRouter router;\n\n    address admin = makeAddr(\"requester\");\n    address treasury = makeAddr(\"treasury\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        factory = new FactoryStub();\n        manager = new PoolManagerStub();\n        venue = new VenueStub();\n        token = factory.deploy(address(manager), LAUNCH_NUMBER, admin);\n        manager.setToken(token);\n        router = new FeeFreeRouter(token, manager);\n\n        vm.startPrank(admin);\n        token.setFeeRecipient(treasury);\n        token.setTradeVenue(address(venue), true);\n        vm.stopPrank();\n    }\n\n    /// A direct sell into the venue pays 2%: treasury 2e18, venue 98e18 (sanity check of intended behaviour).\n    function test_directSellPaysFee() public {\n        factory.send(token, alice, 100 ether);\n        vm.prank(alice);\n        token.transfer(address(venue), 100 ether);\n        assertEq(token.balanceOf(treasury), 2 ether);\n        assertEq(token.balanceOf(address(venue)), 98 ether);\n    }\n\n    /// The same sell routed through the PoolManager must still pay the 2% fee. On the current code it pays nothing.\n    function test_sellRoutedThroughPoolManagerStillPaysFee() public {\n        factory.send(token, alice, 100 ether);\n        vm.startPrank(alice);\n        token.approve(address(router), 100 ether);\n        router.sellToVenue(venue, 100 ether);\n        vm.stopPrank();\n\n        assertEq(token.balanceOf(alice), 0, \"alice sold everything\");\n        assertEq(token.balanceOf(address(manager)), 0, \"nothing stays in the manager\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"the 2% trade fee was bypassed via the PoolManager relay\");\n        assertEq(token.balanceOf(address(venue)), 98 ether, \"venue received the gross amount untaxed\");\n    }\n\n    /// The same buy routed through the PoolManager must still pay the 2% fee. On the current code it pays nothing.\n    function test_buyRoutedThroughPoolManagerStillPaysFee() public {\n        factory.send(token, address(venue), 100 ether);\n        vm.prank(alice);\n        router.buyFromVenue(venue, 100 ether);\n\n        assertEq(token.balanceOf(address(venue)), 0, \"venue paid out everything\");\n        assertEq(token.balanceOf(address(manager)), 0, \"nothing stays in the manager\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"the 2% trade fee was bypassed via the PoolManager relay\");\n        assertEq(token.balanceOf(alice), 98 ether, \"buyer received the gross amount untaxed\");\n    }\n}","reproduction":"State: token deployed by factory F with poolManager M; owner called setFeeRecipient(treasury) and setTradeVenue(V, true) where V is a contract venue; alice holds 100e18 WORK. Direct sell: alice.transfer(V, 100e18) -> treasury +2e18, V +98e18 (intended). Relayed sell: alice approves a router R; R calls M.unlock(); in unlockCallback: M.sync(WORK); WORK.transferFrom(alice, M, 100e18) [exempt, to == poolManager]; M.settle() credits 100e18; M.take(WORK, V, 100e18) [exempt, msg.sender == from == poolManager]. Expected: treasury +2e18, V +98e18. Actual: treasury +0, V +100e18, alice 0, M 0. Relayed buy: V holds 100e18; in unlockCallback: M.sync(WORK); V sends 100e18 to M (what pair.swap(out, 0, M, '') does) [exempt, to == poolManager]; M.settle(); M.take(WORK, alice, 100e18) [exempt]. Expected: treasury +2e18, alice +98e18. Actual: treasury +0, alice +100e18. Run: forge test --match-path test/scratch/PoolManagerRelayBypass.t.sol -> test_sellRoutedThroughPoolManagerStillPaysFee and test_buyRoutedThroughPoolManagerStillPaysFee fail with '0 != 2000000000000000000' on the current code; all three pass when the poolManager clauses are removed from the exemption (checked on a copy of the contract).","severity":"medium","snippet":"            from == address(0) || to == address(0) || msg.sender == launchFactory || msg.sender == poolManager\n                || from == poolManager || to == poolManager || (!isTradeVenue[from] && !isTradeVenue[to])","title":"PoolManager exemption composes into a permissionless fee-free relay around every registered trade venue (merged: economics, permissions, flow, math)"},{"citation":"resolved","description":"setTradeVenue refuses the token, launchFactory, poolManager and launchDistributor() as venues (lines 73-76), but setFeeRecipient only refuses address(0) and the token itself. The owner can therefore route fees to the PoolManager, the factory, the distributor or a registered venue. Fees are written straight into the recipient's balance by super._update (line 123) with no settlement, so at the PoolManager they are unaccounted balance: whoever is inside unlock when the fee lands (for example the trader themselves) syncs, settles and takes it, and anything that lands outside an unlock is absorbed into reserves with no sweep. At a v2 pair they are skim()-able; at the factory or distributor they are stranded. Precondition is an owner mistake, so low; the defect is the asymmetric validation. Fix: apply the same endpoint checks as setTradeVenue in setFeeRecipient (reject launchFactory, poolManager, launchDistributor() and any address with isTradeVenue true).","line":62,"path":"src/Workers.sol","reproduction":"test/scratch/Misc.t.sol::test_setFeeRecipientAcceptsProtectedEndpointsThatSetTradeVenueRefuses: with owner pranked, setTradeVenue(poolManager, true), setTradeVenue(factory, true) revert InvalidTradeVenue, while setFeeRecipient(poolManager), setFeeRecipient(factory), setFeeRecipient(distributor) and setFeeRecipient(registeredVenue) all succeed and feeRecipient() returns them. test_feeSentToPoolManagerIsTakenByTheTrader: owner sets feeRecipient = poolManager, venue V holds 100e18; a trader contract calls M.unlock(), inside: M.sync(); V.send(trader, 100e18) [taxed: fee 2e18 goes to M, trader gets 98e18]; M.settle() credits 2e18; M.take(trader, 2e18). Expected: 2e18 reaches a treasury the requester controls. Actual: trader balance 100e18, M 0, treasury 0 (the trader was refunded their own fee). Both tests pass on the current code, demonstrating the behaviour.","severity":"low","snippet":"        if (recipient == address(0) || recipient == address(this)) revert InvalidFeeRecipient(recipient);","title":"setFeeRecipient accepts the PoolManager, factory, distributor and registered venues, where fees are lost or claimable by anyone (merged: economics, permissions)"},{"citation":"resolved","description":"Ownable(initialOwner_) rejects only address(0). The README (line 28) states the factory must never be substituted into this slot, yet the constructor does not enforce it: factory_ or poolManager_ as owner deploys successfully, supply and all launch flows pass the floor, and afterwards setFeeRecipient/setTradeVenue/transferOwnership are callable only by a contract with no forwarding function, with renounceOwnership disabled. The README deployment table (line 26) also sources the slot from `$requester`, which the custom-token manifest resolves only for application contracts, not the token; the committed launch.json correctly uses the literal remainderTo address 0xbb85c1b7540d9e7b56f7a595fc1ff7dd07cb0823, so the current manifest is not affected. Low: it needs a wrong deployment input, but the consequence is permanent and the guard is one line: revert if initialOwner_ == factory_ || initialOwner_ == poolManager_, and change the README table to say the fourth word is a static address equal to the requester's administration address.","line":44,"path":"src/Workers.sol","reproduction":"test/scratch/Misc.t.sol::test_constructorAcceptsFactoryAsOwnerAndFeeIsThenUnconfigurable: from the factory contract, new Workers(address(this), poolManager, 42, address(this)). Expected: constructor reverts. Actual: deploys; owner() == factory, totalSupply == 1e27 to the factory; requester's setFeeRecipient(treasury) and transferOwnership(requester) revert OwnableUnauthorizedAccount(requester). test_constructorAcceptsPoolManagerAsOwner: new Workers(factory, poolManager, 43, poolManager) deploys with owner() == poolManager. Manifest check: launch.json constructorArgs[3] == economics.remainderTo, so this launch's inputs are correct.","severity":"low","snippet":"    constructor(address factory_, address poolManager_, uint64 launchNumber_, address initialOwner_)\n        ERC20(\"Workers\", \"WORK\")\n        Ownable(initialOwner_)\n    {","title":"Constructor accepts the factory or PoolManager as initialOwner_, which makes the fee permanently unconfigurable; README names the slot `$requester`, which the token manifest cannot resolve (merged: fl"},{"citation":"resolved","description":"DEPENDENCIES.md says the library files are unmodified upstream release sources and tells the reader to verify with sha256sum --check. That command fails for seven forge-std files. I fetched the files from the foundry-rs/forge-std v1.9.7 tag: the recorded hashes match upstream, so the committed copies are what changed. With all whitespace stripped the committed and upstream files hash identically, so the change is line re-wrapping only (forge fmt) with no semantic change, and forge-std is test-only. All eight OpenZeppelin v5.0.2 files the production contract compiles against are byte-identical. Impact: the documented provenance check a reviewer or operator relies on fails, and the docs cannot tell them whether the harness was tampered with. Fix: restore the upstream bytes of the seven files (and exclude lib/ from forge fmt), or regenerate DEPENDENCIES.sha256 from the committed files and amend the 'unmodified' claim to say the forge-std copies were reformatted.","line":10,"path":"DEPENDENCIES.md","reproduction":"Run `sha256sum --check DEPENDENCIES.sha256` in the repository root. Expected: every line OK. Actual: lib/forge-std/src/StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, interfaces/IERC7540.sol, interfaces/IMulticall3.sol: FAILED; 'WARNING: 7 computed checksums did NOT match'. Fetching each from https://raw.githubusercontent.com/foundry-rs/forge-std/v1.9.7/src/<file>: sha256 of the upstream file equals the recorded hash for all seven; `tr -d ' \\n\\t\\r' < upstream | sha256sum` equals the same over the committed file for all seven (whitespace-only difference). The 8 openzeppelin lines all report OK.","severity":"low","snippet":"The library files are unmodified upstream release sources. Their SHA-256 hashes are recorded in `DEPENDENCIES.sha256`; verify using `sha256sum --check DEPENDENCIES.sha256`. Foundry and solc binaries are not vendored: the verification environment provides the pinned compiler.","title":"DEPENDENCIES.md provenance claim is false: 7 vendored forge-std files are not byte-identical to the recorded v1.9.7 hashes (reformatted)"},{"citation":"resolved","description":"The objective is 'Trade fee 2%'. The only market the launch creates is the v4 pool inside the PoolManager; every transfer touching the PoolManager is exempt in _update and setTradeVenue refuses the PoolManager as a venue, so until the owner stands up and registers a separate non-v4 venue the token collects no fee at all. This follows the launch floor (PoolManager flows must be exact, so a transfer tax cannot apply there) and is stated in README line 5 and the launch.json notes. Reported at info so the requester sees that the delivered economics collect fees only on owner-registered secondary venues; collecting on the launch pool would need a different mechanism (a v4 hook delivered as an application contract), which is a scope decision rather than a defect in this code.","line":73,"path":"src/Workers.sol","reproduction":"Owner calls setFeeRecipient(treasury), then setTradeVenue(poolManager, true): reverts InvalidTradeVenue(poolManager) at line 74. Factory seeds the pool (msg.sender == factory, exempt). Trader buys 100e18 from the pool: PoolManager.take transfers with msg.sender == from == poolManager -> treasury stays 0. Trader sells 100e18 back: to == poolManager -> treasury stays 0. Expected per the one-line objective: 2e18 per trade. Actual: 0 on every launch-pool trade; the project's own test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive asserts treasury == 0 after a manager buy and sell with fees active.","severity":"info","snippet":"            if (\n                venue.code.length == 0 || venue == address(this) || venue == launchFactory || venue == poolManager\n                    || venue == launchDistributor()\n            ) revert InvalidTradeVenue(venue);","title":"By construction the 2% fee cannot apply to the launch pool or any Uniswap v4 pool; at launch the fee collects nothing (documented scope limitation)"}],"hash":"235cce285c71c4c6c80fdf17e9b8308cda1107edc156fd50e756c95b1784f7bc","nodeId":"be2b4be2-a052-49dc-9cd7-ed53650f2513","outcome":"completed","summary":"Review complete. The findings file is written, every snippet matches the tree, scratch files are removed, and the working tree is clean apart from the deliverable.\n\n**Outcome:** 5 findings kept from 11 specialist reports, with all 7 entry points covered.\n\n**What I did**\n\n- Ran all four attached proofs: each fails on the current code for the stated reason, and they all share one root cause. I merged them into finding 1 and confirmed on a fixed copy of the contract that the proof passes with the proposed fix while the project's own 56 tests still define the tradeoff.\n- Wrote scratch tests that reproduce the fee-recipient and owner-guard leads, and fetched forge-std v1.9.7 from upstream to confirm the seven mismatched files differ by whitespace only.\n- Read the contract once more myself. The only external call is a static view call inside try/catch, supply is fixed at the constructor, and no privileged path moves or freezes a balance. I found nothing beyond what the specialists reported.\n\n**Findings kept**\n\n1. **Medium.** The PoolManager exemption in `_update` composes with v4's public unlock/sync/settle/take into a fee-free relay around every registered venue. Four specialists reported it; proof attached and verified.\n2. **Low.** The fee-recipient setter accepts the PoolManager, factory, distributor and venues that the venue setter refuses. A fee routed to the PoolManager is taken back by the trader in my scratch test.\n3. **Low.** The constructor accepts the factory or PoolManager as owner, leaving the fee permanently unconfigurable. The committed manifest uses the correct literal address, so this launch is unaffected.\n4. **Low.** The dependency provenance check fails for seven forge-std files. Confirmed as reformatting only.\n5. **Info.** The launch pool collects no fee by construction. Documented and required by the launch floor, so recorded as a scope limitation rather than a defect.\n\n**Dropped:** the fee-rounding note, because the behaviour is documented and bounded below one wei per transfer. It appears as a `holds` row in coverage instead.","treeHash":null,"usage":{"cachedInputTokens":685378,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":21605,"runtime":"claude","turns":22,"wallClockMs":298707}},{"artifacts":[],"attempt":1,"bundleHash":"f984eb2bba3d215250e0b5d8098d24d6745c8fdec3cbc258bdd14010af17bf21","device":"f90b809929565af3","findings":[],"hash":"4401d3272058ac44cddf58715437018f05fa8e15db881821c87acb1a4c8c4584","nodeId":"b2354d9b-e40e-461a-b90f-c1a159167427","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/e16fbe31-a5c8-46af-a344-9a04ec62b450/b2354d9b-e40e-461a-b90f-c1a159167427/launch.json) with exact supply, economics, constructor arguments, and notes explaining fee configuration and exemptions.\n\nSchema and ABI checks passed. `forge build` succeeded; all 40 tests passed. Only `launch.json` changed.","treeHash":"46c693c2668600ed76ff53104319ea64a94a7db4","usage":{"cachedInputTokens":230912,"inputTokens":24793,"model":"gpt-6-astra","outputTokens":3965,"runtime":"codex","turns":3,"wallClockMs":130564}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dc34db8e17664ebd","findings":[{"citation":"resolved","description":"Boundary: the exemption branch at src/Workers.sol:99-102 treats any transfer where msg.sender, from or to is the launch PoolManager as launch settlement and skips the fee. Assumption: such transfers are pool trades or seed settlement. Actual: the Uniswap v4 PoolManager is a permissionless flash-accounting custodian. Any account may call unlock(), and inside its callback sync(WORK) -> transfer WORK to the manager -> settle() to earn a credit, then take(WORK, to, amount) to ANY address; take() is only gated by onlyWhenUnlocked and net-zero deltas, not by pool membership or by `to`. Combined with this token's branch, that gives every holder an untaxed two-hop route around every registered venue: (1) user -> manager is exempt because to == poolManager; (2) manager -> venue is exempt because msg.sender == poolManager. The buy direction is symmetric: venue -> manager is exempt (to == poolManager), then take() to the buyer is exempt (msg.sender == poolManager). Note that the manager clauses are only effective when a venue IS involved: when neither side is a venue the last clause already exempts the transfer, so these clauses exist solely to exempt manager<->venue hops. The direct path user -> venue pays 2 WORK per 100 WORK; the relayed path pays 0. Any aggregator or user routing v4 -> v2-pair (or any registered venue) in one transaction already does exactly this, so the feeRecipient (requester treasury) collects nothing from venues that are reachable via v4 flash accounting, which is all of them. Victim: feeRecipient loses the fee revenue the token exists to collect; no holder loses principal, hence medium (broken guarantee). Minimal fix, compatible with the launch floor: delete `msg.sender == poolManager || from == poolManager || to == poolManager` from the exemption at lines 100-101 (keep msg.sender == launchFactory and the distributor exemption). Manager transfers whose other side is not a venue stay exempt via `!isTradeVenue[from] && !isTradeVenue[to]`, so factory seed, trader buy/sell against the manager, distributor forwarding and claims are unchanged (the protected harness never involves a venue). Only manager<->venue hops become taxed, which is the documented fee semantics. Tradeoff to decide: the README (line 37) and test/WorkersLaunch.t.sol lines 41-46 and 49-58 currently promise that manager->venue settlement arrives whole; those statements and tests would need to change. The fixed variant was checked locally: the attached proof passes on it.","line":100,"path":"src/Workers.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Workers} from \"src/Workers.sol\";\n\n/// @dev Stands in for the launch factory: deploys the token and answers distributorOf.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address manager, uint64 number, address owner) external returns (Workers) {\n        return new Workers(address(this), manager, number, owner);\n    }\n}\n\n/// @dev A registered trading venue (think: a Uniswap v2 pair). It only needs to be able to send tokens.\ncontract VenueStub {\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\ninterface IUnlockCallback {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @dev Minimal model of the Uniswap v4 PoolManager flash-accounting surface that matters here:\n/// anyone may `unlock`, and inside the callback may `sync` + transfer + `settle` to earn credit for a\n/// currency, then `take(currency, to, amount)` to ANY `to`, as long as the net delta is zero at the end.\n/// The real PoolManager exposes exactly these functions with the same permissions (take is\n/// `onlyWhenUnlocked`, not restricted to pools or to the caller's own address).\ncontract PoolManagerModel {\n    address internal locker;\n    address internal syncedCurrency;\n    uint256 internal syncedReserve;\n    int256 internal delta; // positive = credit owed to locker, negative = debt\n\n    function unlock(bytes calldata data) external returns (bytes memory result) {\n        require(locker == address(0), \"already unlocked\");\n        locker = msg.sender;\n        result = IUnlockCallback(msg.sender).unlockCallback(data);\n        require(delta == 0, \"currency not settled\");\n        locker = address(0);\n    }\n\n    function sync(address currency) external {\n        syncedCurrency = currency;\n        syncedReserve = Workers(currency).balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        require(msg.sender == locker, \"not locker\");\n        paid = Workers(syncedCurrency).balanceOf(address(this)) - syncedReserve;\n        delta += int256(paid);\n    }\n\n    function take(address currency, address to, uint256 amount) external {\n        require(msg.sender == locker, \"not locker\");\n        delta -= int256(amount);\n        Workers(currency).transfer(to, amount);\n    }\n}\n\n/// @dev An ordinary user who relays WORK through the PoolManager instead of transferring directly.\ncontract Relayer is IUnlockCallback {\n    PoolManagerModel internal immutable manager;\n    Workers internal immutable token;\n\n    constructor(PoolManagerModel manager_, Workers token_) {\n        manager = manager_;\n        token = token_;\n    }\n\n    /// Sell path: user -> venue, but routed user -> manager -> venue.\n    function sellVia(address venue, uint256 amount) external {\n        manager.unlock(abi.encode(uint8(0), venue, amount));\n    }\n\n    /// Buy path: venue -> user, but routed venue -> manager -> user.\n    function buyVia(VenueStub venue, uint256 amount) external {\n        manager.unlock(abi.encode(uint8(1), address(venue), amount));\n    }\n\n    function unlockCallback(bytes calldata data) external override returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 kind, address venue, uint256 amount) = abi.decode(data, (uint8, address, uint256));\n        manager.sync(address(token));\n        if (kind == 0) {\n            // to == poolManager: the token exempts this hop.\n            token.transfer(address(manager), amount);\n            manager.settle();\n            // msg.sender == poolManager: the token exempts this hop too, although `to` is a venue.\n            manager.take(address(token), venue, amount);\n        } else {\n            // from == venue, to == poolManager: exempt because of the manager.\n            VenueStub(venue).send(token, address(manager), amount);\n            uint256 credited = manager.settle();\n            // msg.sender == poolManager: exempt. The relayer takes whatever the manager credited.\n            manager.take(address(token), address(this), credited);\n        }\n        return \"\";\n    }\n}\n\ncontract PoolManagerRelayBypassTest is Test {\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    FactoryStub factory;\n    PoolManagerModel manager;\n    VenueStub venue;\n    Workers token;\n    Relayer relayer;\n    address admin = makeAddr(\"admin\");\n    address treasury = makeAddr(\"treasury\");\n\n    function setUp() public {\n        factory = new FactoryStub();\n        manager = new PoolManagerModel();\n        venue = new VenueStub();\n        token = factory.deploy(address(manager), 7, admin);\n        vm.startPrank(admin);\n        token.setFeeRecipient(treasury);\n        token.setTradeVenue(address(venue), true);\n        vm.stopPrank();\n        relayer = new Relayer(manager, token);\n    }\n\n    /// A direct sell into the venue pays the fee: this is the behaviour the relay must also produce.\n    function test_directSellIntoVenuePaysTwoPercent() public {\n        vm.prank(address(factory));\n        token.transfer(address(relayer), 100 ether);\n        vm.prank(address(relayer));\n        token.transfer(address(venue), 100 ether);\n        assertEq(token.balanceOf(address(venue)), 98 ether);\n        assertEq(token.balanceOf(treasury), 2 ether);\n    }\n\n    /// Same economic action (100 WORK from a user into the venue), relayed through the PoolManager.\n    /// Expected: the venue receives 98 WORK and the treasury 2 WORK. Actual on current code: the venue\n    /// receives 100 WORK and the treasury 0.\n    function test_sellRelayedThroughPoolManagerStillPaysTwoPercent() public {\n        vm.prank(address(factory));\n        token.transfer(address(relayer), 100 ether);\n\n        relayer.sellVia(address(venue), 100 ether);\n\n        assertEq(token.balanceOf(address(relayer)), 0, \"relayer kept tokens\");\n        assertEq(token.balanceOf(address(manager)), 0, \"manager kept tokens\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"fee bypassed on sell relayed through the PoolManager\");\n        assertEq(token.balanceOf(address(venue)), 98 ether, \"venue received the gross amount\");\n    }\n\n    /// Same for the buy direction: 100 WORK from the venue to a user, relayed through the PoolManager.\n    function test_buyRelayedThroughPoolManagerStillPaysTwoPercent() public {\n        vm.prank(address(factory));\n        token.transfer(address(venue), 100 ether);\n\n        relayer.buyVia(venue, 100 ether);\n\n        assertEq(token.balanceOf(address(venue)), 0, \"venue kept tokens\");\n        assertEq(token.balanceOf(address(manager)), 0, \"manager kept tokens\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"fee bypassed on buy relayed through the PoolManager\");\n        assertEq(token.balanceOf(address(relayer)), 98 ether, \"buyer received the gross amount\");\n    }\n}","reproduction":"State: token deployed by factory F with poolManager M (real v4 PoolManager or the faithful model in the proof); owner called setFeeRecipient(treasury) and setTradeVenue(V, true) where V has code. Alice holds 100e18 WORK. Direct: alice.transfer(V, 100e18) -> V +98e18, treasury +2e18. Relayed: alice (via a contract) calls M.unlock(data); in unlockCallback: M.sync(WORK); WORK.transfer(M, 100e18) [to == M -> exempt, 100e18 arrives]; M.settle() [credit 100e18]; M.take(WORK, V, 100e18) [msg.sender == M -> exempt, 100e18 arrives]. Expected: V 98e18, treasury 2e18. Actual: V 100e18, treasury 0, M 0, alice 0. Buy direction: V.send(M, 100e18) is exempt (to == M), then M.take(WORK, buyer, 100e18) is exempt (msg.sender == M): buyer 100e18, treasury 0 instead of 98e18 / 2e18. Run: forge test --match-path test/scratch/PoolManagerRelayBypass.t.sol -> 2 of 3 tests fail on current code with `fee bypassed ... 0 != 2000000000000000000`; all 3 pass when the poolManager clauses are removed from the exemption.","severity":"medium","snippet":"        if (\n            from == address(0) || to == address(0) || msg.sender == launchFactory || msg.sender == poolManager\n                || from == poolManager || to == poolManager || (!isTradeVenue[from] && !isTradeVenue[to])\n        ) {","title":"PoolManager exemption lets anyone relay WORK to or from a registered venue untaxed (2% fee fully bypassable)"},{"citation":"resolved","description":"Math precision, seam boundary x precision: fee = amount * 200 / 10000 truncates toward zero, so for amount < 50 the fee is 0 and the TradeFeePaid event is skipped (line 122 guard). For amount = 99 the fee is 1 (1.98 truncated), an under-collection of 0.98 wei. The Pashov math guide says fees should round up (protocol-favoring). Impact is bounded to < 1 wei per transfer and does not compound; exploiting it to move 1 WORK untaxed would take about 2e16 transfers, so gas makes it irrelevant. The README (line 11) documents this rounding and accepts it. Reported for completeness of the math audit only; no change required. If the author prefers protocol-favoring rounding: fee = (amount * TRADE_FEE_BPS + BPS_DENOMINATOR - 1) / BPS_DENOMINATOR; this cannot overflow since amount <= 1e27, and amount - fee still cannot underflow because fee <= ceil(amount/50) <= amount for amount >= 1 (fee is 0 for amount 0).","line":121,"path":"src/Workers.sol","reproduction":"Owner sets feeRecipient and registers venue V. Fund V with 1000 wei via the factory. V.transfer(alice, 49): expected under round-up 1 wei fee, actual fee 0 (treasury stays 0, alice +49). V.transfer(alice, 99): fee 1 wei (99*200/10000 = 1.98 -> 1), alice +98. Verified in test/scratch/DecodeBoundary.t.sol::test_feeRoundsDownBelow50Wei and by the project's own test_RoundingAtSmallestFeeUnitAndZeroTransfers.","severity":"info","snippet":"        uint256 fee = (amount * TRADE_FEE_BPS) / BPS_DENOMINATOR;","title":"Trade fee rounds down: transfers below 50 wei pay no fee (documented, dust-level)"}],"hash":"61f397be5d638b16e9c5141c2dcf496c75ef8fee8d6ec6893fcf520d1a1d6253","nodeId":"a1c2bde5-c685-4937-942c-a69ea49c3c02","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json` with two findings and twelve coverage rows.\n\n**Main finding (medium): the 2% venue fee can be bypassed by anyone by relaying through the PoolManager.** The exemption branch at `src/Workers.sol:100` skips the fee whenever the PoolManager is the caller, sender or receiver. Those three clauses only take effect when a registered venue is on the other side, since transfers with no venue are already exempt by the last clause. The real Uniswap v4 PoolManager lets any unlocker sync, settle and then `take` to an arbitrary address, so a user moves WORK to the manager untaxed and the manager forwards it to the venue untaxed. The buy direction is symmetric.\n\n- Direct sell of 100 WORK into a venue: venue gets 98, treasury gets 2.\n- Same sell relayed through the manager: venue gets 100, treasury gets 0.\n- Proof at `test/scratch/PoolManagerRelayBypass.t.sol` fails two of three tests on the current code. It passes on a local variant with the PoolManager clauses removed from the exemption, which leaves every launch-floor flow untaxed because none of them involves a venue. I flagged the tradeoff: the README and two existing launch tests promise manager-to-venue settlement arrives whole and would need to change.\n\n**Info finding: fee rounds down.** Amounts below 50 wei pay nothing and 99 wei pays 1 wei. This is documented in the README and bounded to under 1 wei per transfer, so I reported it only for completeness of the math audit.\n\n**What held.** No overflow is possible since the balance check at lines 117 and 118 bounds the amount to the fixed supply before the multiplication. The fee never exceeds the amount, so no underflow. Conservation is exact across both hops, including when the fee recipient is the sender, the receiver, or a venue. Zero and full-balance amounts behave correctly. The distributor lookup is a static call with the revert caught. One boundary note is recorded in coverage rather than as a finding: a factory that returns empty data instead of reverting produces a bare revert rather than the custom error, which is harmless since the launch factory defines that function.\n\nAll seven listed entry points have coverage rows, with three marked as touched by the main finding. The existing 40-test suite passes unchanged. Nothing outside `test/scratch/` and the findings file was modified.","treeHash":null,"usage":{"cachedInputTokens":1147752,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":28910,"runtime":"claude","turns":31,"wallClockMs":469985}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"232b62e021f6f394","findings":[{"citation":"resolved","description":"_update exempts a transfer whenever `to == poolManager` (regardless of who `from` is) and whenever `msg.sender == poolManager` (regardless of who `to` is). Uniswap v4's PoolManager exposes `unlock`, `sync`, `settle` and `take` to any caller; the only requirement is that the caller's deltas net to zero inside the unlock callback. Those two exemptions therefore compose into a permissionless pass-through: any trader can have a registered venue pay its swap output straight into the PoolManager (from = venue, to = poolManager -> exempt), call `settle()` to be credited, and `take()` it back out (msg.sender = poolManager -> exempt). The sell direction is symmetric: trader -> PoolManager (exempt), `take(WORK, venue, x)` (exempt), then swap on the venue. The 2% trade fee, which is the token's only feature beyond plain ERC-20, is thus avoidable on every venue the owner registers by anyone who wraps the trade in a 40-line contract; aggregators and MEV bots would route this way by default. The native pool is already fee-free by design, so after this bypass the fee binds only on naive direct interactions. Asymmetry: `setTradeVenue` refuses to register the PoolManager as a venue (line 74), but the venue classification of the counterparty is ignored on the manager legs; the guard on the owner side has no mirror on the transfer side. Fix (preserves the launch flows the floor requires, which involve no registered venues): keep the PoolManager exemption for legs whose counterparty is not a venue, and charge the fee when a registered venue is the counterparty of a PoolManager leg, e.g. exempt when `((msg.sender == poolManager || from == poolManager) && !isTradeVenue[to]) || (to == poolManager && !isTradeVenue[from])`. Note this is a scope decision: the existing tests test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive, test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount and test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers encode the current behaviour and would need to change; the protected floor passes unchanged with the fix because it registers no venues. If instead the requester accepts that venue fees are voluntary, the README's claim that registered venues are taxed should be corrected.","line":100,"path":"src/Workers.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Workers} from \"src/Workers.sol\";\n\n/// @dev Stands in for the launch factory: deploys the token and answers distributorOf.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address manager, uint64 number, address owner) external returns (Workers) {\n        return new Workers(address(this), manager, number, owner);\n    }\n\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\ninterface IUnlockCallback {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @dev Minimal model of Uniswap v4 PoolManager's permissionless settlement surface:\n/// unlock -> callback, sync/settle credit whatever arrived, take pays out against that credit.\n/// Anyone may call these; the real manager only requires the deltas to net to zero.\ncontract PoolManagerStub {\n    Workers public token;\n    uint256 private reserves;\n    bool private unlocked;\n    mapping(address => int256) public delta;\n\n    function setToken(Workers token_) external {\n        token = token_;\n    }\n\n    function unlock(bytes calldata data) external returns (bytes memory result) {\n        require(!unlocked, \"already unlocked\");\n        unlocked = true;\n        result = IUnlockCallback(msg.sender).unlockCallback(data);\n        require(delta[msg.sender] == 0, \"currency not settled\");\n        unlocked = false;\n    }\n\n    function sync() external {\n        reserves = token.balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        require(unlocked, \"locked\");\n        paid = token.balanceOf(address(this)) - reserves;\n        delta[msg.sender] += int256(paid);\n    }\n\n    function take(address to, uint256 amount) external {\n        require(unlocked, \"locked\");\n        delta[msg.sender] -= int256(amount);\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev A registered trading venue (think: a Uniswap v2 pair). Its swap pays out to whatever\n/// address the caller names, exactly as a v2 pair's swap(amount0Out, amount1Out, to, data) does.\ncontract VenueStub {\n    Workers public token;\n\n    constructor(Workers token_) {\n        token = token_;\n    }\n\n    function swapOut(address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev An ordinary trader that routes a venue buy through the pool manager's sync/settle/take.\ncontract RelayTrader is IUnlockCallback {\n    PoolManagerStub public manager;\n    VenueStub public venue;\n    Workers public token;\n\n    constructor(PoolManagerStub manager_, VenueStub venue_, Workers token_) {\n        manager = manager_;\n        venue = venue_;\n        token = token_;\n    }\n\n    function buyFromVenue(uint256 amount) external {\n        manager.unlock(abi.encode(amount));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        uint256 amount = abi.decode(data, (uint256));\n        manager.sync();\n        // The venue pays its swap output straight into the pool manager: from == venue, to == poolManager.\n        venue.swapOut(address(manager), amount);\n        uint256 credited = manager.settle();\n        // The pool manager pays the trader out whatever was credited: msg.sender == poolManager.\n        manager.take(address(this), credited);\n        return \"\";\n    }\n}\n\ncontract PoolManagerRelayBypassTest is Test {\n    FactoryStub factory;\n    PoolManagerStub manager;\n    VenueStub venue;\n    Workers token;\n    address admin = makeAddr(\"requester\");\n    address treasury = makeAddr(\"treasury\");\n\n    function setUp() public {\n        factory = new FactoryStub();\n        manager = new PoolManagerStub();\n        token = factory.deploy(address(manager), 41, admin);\n        manager.setToken(token);\n        venue = new VenueStub(token);\n\n        vm.startPrank(admin);\n        token.setFeeRecipient(treasury);\n        token.setTradeVenue(address(venue), true);\n        vm.stopPrank();\n\n        factory.send(token, address(venue), 1_000 ether);\n    }\n\n    /// @dev A direct buy from the registered venue pays the 2% fee.\n    function test_directVenueBuyPaysFee() public {\n        address direct = makeAddr(\"direct\");\n        venue.swapOut(direct, 100 ether);\n        assertEq(token.balanceOf(direct), 98 ether);\n        assertEq(token.balanceOf(treasury), 2 ether);\n    }\n\n    /// @dev The same buy routed through the pool manager's settlement surface must pay the same fee.\n    /// On the current code it pays nothing: the venue -> poolManager leg and the poolManager -> trader\n    /// leg are both exempt, so the pool manager is a fee-free relay around every registered venue.\n    function test_venueBuyRoutedThroughPoolManagerStillPaysFee() public {\n        RelayTrader trader = new RelayTrader(manager, venue, token);\n        trader.buyFromVenue(100 ether);\n\n        assertEq(token.balanceOf(address(venue)), 900 ether, \"venue paid out the gross amount\");\n        assertEq(token.balanceOf(address(manager)), 0, \"nothing stays in the pool manager\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"trade fee was bypassed by relaying through the pool manager\");\n        assertEq(token.balanceOf(address(trader)), 98 ether, \"trader received the gross amount fee-free\");\n    }\n}","reproduction":"State: owner has called setFeeRecipient(treasury) and setTradeVenue(V, true) where V is a contract (e.g. a Uniswap v2 pair) holding 1000 WORK. Baseline: V.transfer(buyer, 100e18) -> buyer 98e18, treasury 2e18 (fee charged). Bypass: trader T calls poolManager.unlock(); in T.unlockCallback: poolManager.sync(WORK); V pays its swap output with WORK.transfer(poolManager, 100e18) [from = V (venue), to = poolManager -> first branch exempt, no fee, no TradeFeePaid]; paid = poolManager.settle() == 100e18; poolManager.take(WORK, T, paid) -> poolManager calls WORK.transfer(T, 100e18) [msg.sender = poolManager -> exempt]. Result: V -100e18, T +100e18, treasury +0. Expected: treasury +2e18 and T +98e18 as in the direct trade. Proof test test/scratch/PoolManagerRelayBypass.t.sol (models the manager's unlock/sync/settle/take with a stub) fails on the current code with `0 != 2000000000000000000` and passes with the fix above.","severity":"medium","snippet":"            from == address(0) || to == address(0) || msg.sender == launchFactory || msg.sender == poolManager\n                || from == poolManager || to == poolManager || (!isTradeVenue[from] && !isTradeVenue[to])","title":"PoolManager exemption is a fee-free relay around every registered trade venue (trust gap: access x economics x asymmetry)"},{"citation":"resolved","description":"setTradeVenue(venue, true) rejects address(this), launchFactory, poolManager and the registered distributor (line 74-76), but setFeeRecipient only rejects address(0) and address(this). The fee destination can therefore be set to the PoolManager, the factory, the distributor, or a registered venue. Fees are credited by direct balance writes (super._update at line 123) with no settlement callback, so at the PoolManager they are unaccounted reserves: any caller inside `unlock` can `sync(WORK)`, trigger a venue trade that pays a fee, `settle()` to be credited with the fee, and `take()` it. At a Uniswap v2 pair they are `skim()`-able by anyone. At the factory or distributor they are stranded unless those contracts expose a sweep. The owner's call is the precondition, which keeps severity low, but the asymmetric validation is the defect: the owner is protected from the same mistake on the venue side. Fix: mirror the protected-endpoint checks in setFeeRecipient (reject launchFactory, poolManager and launchDistributor()).","line":62,"path":"src/Workers.sol","reproduction":"Owner calls setFeeRecipient(poolManager) -> succeeds (test/scratch/FeeRecipientEndpoint.t.sol test_protectedEndpointsRejectedAsVenueButAcceptedAsFeeRecipient shows setTradeVenue reverts with InvalidTradeVenue for poolManager, launchFactory and the distributor while setFeeRecipient accepts all three). Then with venue V registered and holding 100e18: V.transfer(buyer, 100e18) -> buyer 98e18, poolManager balance +2e18 (test_feesSentToPoolManagerLeaveTreasuryEmpty). Expected: the setter refuses the endpoint, as the venue setter does. Actual: 2e18 sits in the PoolManager as unaccounted balance, claimable by the next unlock caller via sync/settle/take.","severity":"low","snippet":"        if (recipient == address(0) || recipient == address(this)) revert InvalidFeeRecipient(recipient);","title":"setFeeRecipient accepts the protected endpoints that setTradeVenue refuses; fees sent there are sweepable by anyone"},{"citation":"resolved","description":"Not a code defect; recorded for the manifest reviewer. The README maps the fourth constructor argument to $requester, but the token constructor's symbolic set is $factory, $poolManager and $launchNumber only; $requester resolves for application contracts. The owner therefore has to be written into token.constructorArgs[3] as a static address word. The constructor validates factory_ and poolManager_ (code present, factory == msg.sender) but can only reject zero for the owner. The owner controls fee routing and venue classification (can tax any contract's transfers at 2% by registering it, as the README documents), renounceOwnership is disabled and only the owner can start a two-step transfer, so a mistaken literal is permanent. The owner cannot mint, burn, pause, freeze or move balances: this is a fee-configuration power only.","line":44,"path":"src/Workers.sol","reproduction":"Manifest state: token.constructorArgs = [\"$factory\", \"$poolManager\", \"$launchNumber\", \"0x<address not controlled by the requester>\"]. After launch the requester calls setFeeRecipient(treasury) -> reverts OwnableUnauthorizedAccount(requester); no venue can ever be registered by the requester and no recovery path exists. With \"0x0000000000000000000000000000000000000000\" the constructor reverts OwnableInvalidOwner and the launch transaction fails. Expected: the manifest's fourth word is checked against economics.remainderTo or the requester's attested address at review time.","severity":"info","snippet":"    constructor(address factory_, address poolManager_, uint64 launchNumber_, address initialOwner_)","title":"Trust assumption: initialOwner_ must be supplied as a literal address; a wrong literal installs an unrecoverable owner"}],"hash":"6a037cf2b87f32fc5e15bedc3238c1d7a95ef1e76d63c00866caab870737eb2f","nodeId":"d036fc29-8063-4617-9525-e4c470ef2221","outcome":"completed","summary":"Review complete. The findings are in `.imd-findings.json` at the repository root, and no source or configuration file was changed. The two scratch tests live under `test/scratch/`, which git ignores.\n\n**What I found**\n\n- **Medium: the PoolManager exemption is a fee-free relay around every registered venue.** In `_update` at `src/Workers.sol:100`, a transfer is exempt whenever the receiver is the PoolManager, regardless of the sender, and whenever the PoolManager is the caller, regardless of the receiver. Uniswap v4's `unlock`, `sync`, `settle` and `take` are open to anyone, so a trader can have a registered venue pay its swap output into the PoolManager, settle, and take it back out, and neither leg pays the 2% fee. The sell direction works the same way. The proof test fails on the current code with treasury receiving nothing instead of 2 WORK on a 100 WORK trade. I verified a minimal fix, which keeps PoolManager legs exempt only when the counterparty is not a registered venue. With it the proof passes and the protected launch floor is unaffected, but three existing project tests that encode the current \"manager-to-venue legs are exempt\" design would need updating, so this is a design decision for the author.\n- **Low: `setFeeRecipient` accepts the endpoints `setTradeVenue` refuses.** The venue setter rejects the factory, PoolManager and distributor, but the fee recipient setter at `src/Workers.sol:62` only rejects zero and the token itself. Fees routed to the PoolManager become unaccounted reserves that any unlock caller can sweep via sync, settle and take. The owner's misconfiguration is the precondition, which keeps it low.\n- **Info: the owner must be supplied as a literal address.** The fourth constructor argument is documented as the requester, but the token constructor can only resolve the factory, PoolManager and launch number symbols. A wrong literal installs an unrecoverable owner because renunciation is disabled. This is a manifest-review item, not a code defect.\n\n**Coverage**\n\nAll seven listed entry points have rows. `transfer`, `transferFrom` and `setFeeRecipient` point at findings. `acceptOwnership`, `approve`, `setTradeVenue` and `transferOwnership` hold. I also recorded rows for the constructor role handout, the no-privileged-balance-movement invariant, the launch-flow exactness invariant and the distributor lookup. The owner's documented power to tax any contract by registering it is noted as a trust assumption in the `setTradeVenue` row rather than reported as a finding, since no unprivileged amplifier exists. Both static-analysis leads were centralization and literal-size notes with no exploit path.","treeHash":null,"usage":{"cachedInputTokens":967320,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":30937,"runtime":"claude","turns":27,"wallClockMs":439960}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1507f63d3f1b973a","findings":[{"citation":"resolved","description":"_update exempts every transfer whose sender, receiver or caller is the launch PoolManager, before it looks at the venue flags. The v4 PoolManager is a public singleton: any address can call unlock(), and inside its callback call sync(WORK), transfer WORK to the manager, settle() to be credited exactly what arrived, and take(WORK, anyAddress, amount), which makes the manager transfer the tokens out (msg.sender == from == poolManager). No pool, hook or liquidity is needed for these four primitives. So a seller of WORK on a registered venue (e.g. a Uniswap v2 pair) can move user -> PoolManager (exempt: to == poolManager) -> pair (exempt: msg.sender/from == poolManager) and then call pair.swap(); and a buyer can call pair.swap(amountOut, 0, poolManager, '') between sync() and settle() (exempt: to == poolManager) and take() the tokens to themselves (exempt). Every leg is untaxed, so the fee recipient receives nothing for either side of the trade. The only cost is the gas of one extra unlock, so any trader, router or MEV searcher can wrap every venue trade this way, and a public fee-free router contract makes it available to everyone. The same exemption also means every Uniswap v4 pool for WORK that anyone creates in the same PoolManager (any fee tier/hook) trades untaxed, not only the launch pool. Who loses: feeRecipient loses the whole 2% on every routed trade (2 WORK on a 100 WORK trade; 2% of all secondary-venue volume). The launch floor only needs factory-caller, distributor and PoolManager<->trader flows to be exact; it never moves tokens between the PoolManager and a registered venue, and setTradeVenue already refuses the PoolManager, factory and distributor as venues. Minimal fix that keeps the launch flows exact: drop the three poolManager clauses from the first exemption so a transfer is taxed whenever either side is a registered venue unless msg.sender is the factory or the distributor is involved: `if (from == address(0) || to == address(0) || msg.sender == launchFactory || (!isTradeVenue[from] && !isTradeVenue[to]))`. PoolManager<->trader, factory seed and distributor claims stay untaxed (no venue involved); PoolManager<->venue legs become taxed, closing the relay. Verified on a copy: the proof passes and all launch-flow tests pass; the four existing tests that assert PoolManager<->venue transfers arrive whole (test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive, test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount, test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers and the invariant handler's exemption model) encode the exploitable rule and must be updated with the fix. If the requester instead wants PoolManager<->venue transfers to stay exempt, the 2% fee on registered venues is advisory only and should be documented as such.","line":100,"path":"src/Workers.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Workers} from \"src/Workers.sol\";\n\n/// @dev Stands in for the launch factory: deploys the token (constructor requires msg.sender == factory with code)\n/// and answers distributorOf(uint64) the way the factory does.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address manager, uint64 number, address owner) external returns (Workers) {\n        return new Workers(address(this), manager, number, owner);\n    }\n\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev Minimal copy of the Uniswap v4 PoolManager currency-settlement primitives any unlocker may use:\n/// unlock() calls back the caller; sync() records reserves; settle() credits balance - reserves to the caller;\n/// take() transfers credited tokens out to any address. No pool, hook or key is needed for these.\ncontract PoolManagerStub {\n    Workers public token;\n    uint256 private reserves;\n    mapping(address => uint256) public credit;\n\n    function setToken(Workers token_) external {\n        token = token_;\n    }\n\n    function unlock(bytes calldata data) external returns (bytes memory) {\n        return IUnlockCallback(msg.sender).unlockCallback(data);\n    }\n\n    function sync() external {\n        reserves = token.balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        paid = token.balanceOf(address(this)) - reserves;\n        reserves = token.balanceOf(address(this));\n        credit[msg.sender] += paid;\n    }\n\n    function take(address to, uint256 amount) external {\n        credit[msg.sender] -= amount;\n        token.transfer(to, amount); // msg.sender == poolManager, from == poolManager\n        reserves = token.balanceOf(address(this));\n    }\n}\n\ninterface IUnlockCallback {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @dev A registered trading venue, e.g. a Uniswap v2 pair: it holds tokens and sends them wherever the swap's\n/// `to` says (pair.swap(amountOut, 0, to, \"\")). Modelled by a public send.\ncontract VenueStub {\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev Anyone can deploy this. It moves WORK between a user and a registered venue through the PoolManager's\n/// sync/settle/take so that no leg of the transfer is taxable.\ncontract FeeFreeRouter is IUnlockCallback {\n    Workers immutable token;\n    PoolManagerStub immutable manager;\n\n    constructor(Workers token_, PoolManagerStub manager_) {\n        token = token_;\n        manager = manager_;\n    }\n\n    /// Sell: user -> PoolManager (exempt: to == poolManager) -> venue (exempt: msg.sender/from == poolManager).\n    function sellToVenue(VenueStub venue, uint256 amount) external {\n        manager.unlock(abi.encode(true, msg.sender, address(venue), amount));\n    }\n\n    /// Buy: venue -> PoolManager (exempt: to == poolManager) -> user (exempt: msg.sender/from == poolManager).\n    function buyFromVenue(VenueStub venue, uint256 amount) external {\n        manager.unlock(abi.encode(false, msg.sender, address(venue), amount));\n    }\n\n    function unlockCallback(bytes calldata data) external override returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (bool sell, address user, address venue, uint256 amount) = abi.decode(data, (bool, address, address, uint256));\n        manager.sync();\n        if (sell) token.transferFrom(user, address(manager), amount);\n        else VenueStub(venue).send(token, address(manager), amount);\n        uint256 paid = manager.settle();\n        manager.take(sell ? venue : user, paid);\n        return \"\";\n    }\n}\n\ncontract PoolManagerRelayBypassTest is Test {\n    uint64 constant LAUNCH_NUMBER = 41;\n\n    FactoryStub factory;\n    PoolManagerStub manager;\n    VenueStub venue;\n    Workers token;\n    FeeFreeRouter router;\n\n    address admin = makeAddr(\"requester\");\n    address treasury = makeAddr(\"treasury\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        factory = new FactoryStub();\n        manager = new PoolManagerStub();\n        venue = new VenueStub();\n        token = factory.deploy(address(manager), LAUNCH_NUMBER, admin);\n        manager.setToken(token);\n        router = new FeeFreeRouter(token, manager);\n\n        vm.startPrank(admin);\n        token.setFeeRecipient(treasury);\n        token.setTradeVenue(address(venue), true);\n        vm.stopPrank();\n    }\n\n    /// A direct sell into the venue pays 2%: treasury 2e18, venue 98e18 (sanity check of intended behaviour).\n    function test_directSellPaysFee() public {\n        factory.send(token, alice, 100 ether);\n        vm.prank(alice);\n        token.transfer(address(venue), 100 ether);\n        assertEq(token.balanceOf(treasury), 2 ether);\n        assertEq(token.balanceOf(address(venue)), 98 ether);\n    }\n\n    /// The same sell routed through the PoolManager must still pay the 2% fee. On the current code it pays nothing.\n    function test_sellRoutedThroughPoolManagerStillPaysFee() public {\n        factory.send(token, alice, 100 ether);\n        vm.startPrank(alice);\n        token.approve(address(router), 100 ether);\n        router.sellToVenue(venue, 100 ether);\n        vm.stopPrank();\n\n        assertEq(token.balanceOf(alice), 0, \"alice sold everything\");\n        assertEq(token.balanceOf(address(manager)), 0, \"nothing stays in the manager\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"the 2% trade fee was bypassed via the PoolManager relay\");\n        assertEq(token.balanceOf(address(venue)), 98 ether, \"venue received the gross amount untaxed\");\n    }\n\n    /// The same buy routed through the PoolManager must still pay the 2% fee. On the current code it pays nothing.\n    function test_buyRoutedThroughPoolManagerStillPaysFee() public {\n        factory.send(token, address(venue), 100 ether);\n        vm.prank(alice);\n        router.buyFromVenue(venue, 100 ether);\n\n        assertEq(token.balanceOf(address(venue)), 0, \"venue paid out everything\");\n        assertEq(token.balanceOf(address(manager)), 0, \"nothing stays in the manager\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"the 2% trade fee was bypassed via the PoolManager relay\");\n        assertEq(token.balanceOf(alice), 98 ether, \"buyer received the gross amount untaxed\");\n    }\n}","reproduction":"State: token deployed by the factory with poolManager = PM; owner has called setFeeRecipient(treasury) and setTradeVenue(pair, true) where pair is a registered venue contract; alice holds 100e18 WORK. Direct sell: alice.transfer(pair, 100e18) -> treasury +2e18, pair +98e18 (intended). Relayed sell: alice approves Router; Router calls PM.unlock(); in unlockCallback: PM.sync(WORK); WORK.transferFrom(alice, PM, 100e18) [exempt, to == poolManager]; PM.settle() credits 100e18; PM.take(WORK, pair, 100e18) [exempt, msg.sender == from == poolManager]. Expected: treasury +2e18, pair +98e18. Actual: treasury +0, pair +100e18, alice 0, PM 0. Relayed buy: pair holds 100e18; in unlockCallback: PM.sync(WORK); pair.send(PM, 100e18) (what pair.swap(out,0,PM,'') does) [exempt, to == poolManager]; PM.settle(); PM.take(WORK, alice, 100e18). Expected: treasury +2e18, alice +98e18. Actual: treasury +0, alice +100e18. Proof file test/scratch/PoolManagerRelayBypass.t.sol: tests test_sellRoutedThroughPoolManagerStillPaysFee and test_buyRoutedThroughPoolManagerStillPaysFee fail on the current code with '0 != 2000000000000000000' and pass with the fix above.","severity":"medium","snippet":"            from == address(0) || to == address(0) || msg.sender == launchFactory || msg.sender == poolManager\n                || from == poolManager || to == poolManager || (!isTradeVenue[from] && !isTradeVenue[to])","title":"Anyone bypasses the 2% venue fee by relaying transfers through the exempt Uniswap v4 PoolManager (sync/settle/take)"},{"citation":"resolved","description":"The job specifies 'Trade fee 2%'. The only market the launch creates is the Uniswap v4 pool inside the PoolManager, and every transfer touching the PoolManager is exempt in _update (line 100-101), while setTradeVenue refuses the PoolManager as a venue (line 74). So at launch, and until the owner stands up a separate non-v4 market and registers it, the token collects no trade fee on any trade. Buys and sells on the launch pool, and on any other v4 pool anyone creates for WORK, are permanently untaxed. The README states this, but the delivered economics differ from the one-line objective, and the fee recipient's revenue is zero on the primary market by construction. This is a scope decision for the requester rather than a code bug the floor allows fixing in the token: the launch floor requires PoolManager<->trader flows to be exact (a taxed sell would make v4 settle() come up short and the swap revert), so collecting a fee on the launch pool would require a different mechanism (for example a v4 hook contract taking the fee in the pool, delivered as an application contract) rather than a transfer tax. Report as a documented limitation if accepted; otherwise the design needs that extra contract.","line":74,"path":"src/Workers.sol","reproduction":"State: fresh deployment; owner calls setFeeRecipient(treasury). Owner calls setTradeVenue(poolManager, true): reverts InvalidTradeVenue(poolManager) (line 74), so the launch pool can never be a venue. Factory seeds the pool (factory -> PoolManager, exempt). A trader buys 100e18 WORK from the pool: PoolManager.take transfers 100e18 to trader with msg.sender == from == poolManager -> treasury balance stays 0. Trader sells 100e18 back: trader -> PoolManager (to == poolManager) -> treasury stays 0. Expected per objective: 2e18 WORK of fee on each trade. Actual: 0 on every launch-pool trade, forever.","severity":"low","snippet":"                venue.code.length == 0 || venue == address(this) || venue == launchFactory || venue == poolManager","title":"The 2% trade fee cannot apply to the launch pool or any Uniswap v4 pool: at launch the fee collects nothing and no configuration can change that"},{"citation":"resolved","description":"setTradeVenue refuses the token, factory, PoolManager and distributor (line 73-76), but setFeeRecipient only refuses address(0) and the token itself. If the owner sets the recipient to the PoolManager (a plausible mistake: 'send fees to the pool'), each fee is transferred to the manager outside any sync/settle. The v4 PoolManager credits only balance growth after a sync() and has no sweep, so fees that land before anyone syncs are absorbed into reserves and are irrecoverable, while a trader who wraps their own venue trade in unlock/sync/settle/take gets their own 2% fee credited back and takes it, so the fee is paid to the trader rather than to the treasury. A registered venue such as a Uniswap v2 pair as recipient is similar: fees sit unsynced in the pair and anyone can skim() them. Setting the recipient to the factory or distributor parks the fees in launch contracts with no path back to the requester. This is an owner misconfiguration, so it is low; the one-line fix is to apply the same endpoint checks as setTradeVenue (reject launchFactory, poolManager, launchDistributor() and any address with isTradeVenue true) in setFeeRecipient.","line":62,"path":"src/Workers.sol","reproduction":"State: owner has registered venue V; alice holds 100e18. Owner calls setFeeRecipient(poolManager): succeeds (line 62 only rejects address(0) and address(this)), emits FeeRecipientUpdated. alice.transfer(V, 100e18): fee 2e18 is sent to the PoolManager (super._update(from, poolManager, 2e18) at line 123) with no sync. Expected: fee reaches a treasury the requester controls. Actual: PoolManager balance +2e18 with no delta credit for anyone; after the next sync() it is part of reserves and cannot be withdrawn by the owner or the requester. Variant: alice performs the same transfer inside her own PoolManager.unlock between sync() and settle(): settle() credits her 2e18 and take() returns it to her, so the 2% fee is refunded to the trader.","severity":"low","snippet":"        if (recipient == address(0) || recipient == address(this)) revert InvalidFeeRecipient(recipient);","title":"setFeeRecipient accepts the PoolManager, factory, distributor or a registered venue, routing fees where they are lost or free to take"}],"hash":"7380e7477977585ff6dc68ae370d70259276f580866a08178162949c59817cbe","nodeId":"0d330f84-d0fb-4ae6-b039-9c41c96ae729","outcome":"completed","summary":"The review is complete. The findings file `.imd-findings.json` is written and valid, no tracked file changed, and the only untracked files are the findings file and the ignored scratch test.\n\n**Result: one medium and two low findings in the economics of `src/Workers.sol`.**\n\n- **Medium, fee bypass through the PoolManager.** The transfer hook exempts any transfer where the Uniswap v4 PoolManager is sender, receiver or caller, before it checks venue flags. The PoolManager is a public singleton, so anyone can call unlock, sync, settle and take with no pool involved. A trader moves tokens user to PoolManager to venue, or venue to PoolManager to user, and every leg is untaxed. The fee recipient loses the full 2% on every routed trade, and any v4 pool for WORK is also untaxed. The proof test in `test/scratch/PoolManagerRelayBypass.t.sol` fails on the current code in both directions and passes with the fix I verified on a copy: drop the three PoolManager clauses from the first exemption so PoolManager-to-venue legs are taxed. The launch flows still pass with that change. Four existing tests encode the exploitable rule and would need updating.\n- **Low, the launch pool collects no fee.** The objective says \"Trade fee 2%\", but the launch's only market is the v4 pool, which is exempt by construction and cannot be registered as a venue. The fee is zero on the primary market forever unless the requester adds a separate mechanism such as a hook contract. This is a scope decision for the requester.\n- **Low, fee recipient validation is weaker than venue validation.** The owner can set the recipient to the PoolManager, factory, distributor or a venue. Fees sent to the PoolManager outside sync/settle are absorbed into reserves and lost, or a trader can wrap their trade to claim their own fee back.\n\nCoverage has 12 rows: all 7 listed entry points plus 5 invariant and dependency rows. Supply conservation, launch-flow exactness, the view-only registry dependency and the two-step ownership all hold. Noted as a trust assumption rather than a finding: the owner can tax any contract holder by registering it as a venue, and the fourth constructor argument must be a static requester address in the manifest.","treeHash":null,"usage":{"cachedInputTokens":1174707,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":27313,"runtime":"claude","turns":28,"wallClockMs":368471}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2565f234b0a569e9","findings":[{"citation":"resolved","description":"The exemption branch in _update treats the Uniswap v4 PoolManager as exempt whenever it is the caller, the sender or the receiver, and that exemption is evaluated BEFORE the venue check. The PoolManager is a permissionless router: inside unlock() anyone can sync(token), have a third party pay the manager, settle() to be credited the exact amount received, and take(to, amount) to any address. So a trader who buys from an owner-registered venue (a v2-style pair that delivers output to a caller-chosen recipient) names the PoolManager as the recipient: venue -> PoolManager is exempt (to == poolManager), the trader settles the full gross amount, then take(trader) is exempt (msg.sender == poolManager). The treasury receives nothing instead of 2%. Selling works the same way in reverse: pay the manager (exempt), take(venue, amount) (exempt), and the venue receives the gross amount. The exemption therefore protects not only the launch flows the floor requires (factory -> distributor, factory -> manager seed, trader <-> manager on the launch pool, distributor claims) but every transfer that merely touches the PoolManager, which an unprivileged actor can arrange for any registered venue. The README discloses only that the native v4 pool is untaxed, not that the fee on every other venue is avoidable by anyone. Impact: the requested 'Trade fee 2%' is unenforceable on any venue for any trader or aggregator who adds one hop through the PoolManager; the fee recipient loses that revenue. No principal is at risk, so medium. Proposed minimal fix (a design decision for the author): give venue involvement precedence over PoolManager counterparty status, i.e. exempt only `from == address(0) || to == address(0) || msg.sender == launchFactory || (!isTradeVenue[from] && !isTradeVenue[to])` and the distributor, so a transfer between a registered venue and the PoolManager (either direction, any caller) pays the fee. Launch flows stay exact because the factory, the PoolManager and the distributor can never be registered as venues and no launch flow touches a venue; the attached proof passes against that patched copy, while the existing test test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive (manager <-> venue exact) encodes the current precedence and would have to change. If the author instead keeps the current precedence, the README must state that the fee is avoidable via the PoolManager on every venue.","line":100,"path":"src/Workers.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Workers} from \"src/Workers.sol\";\n\n/// @dev Stands in for the launch factory: deploys the token and answers distributorOf.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address manager, uint64 number, address owner) external returns (Workers) {\n        return new Workers(address(this), manager, number, owner);\n    }\n\n    function send(Workers token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev Minimal model of Uniswap v4 PoolManager settlement: anyone may sync, pay in, settle and take.\n/// The real PoolManager exposes exactly this permissionless sync/settle/take surface inside unlock().\ncontract PoolManagerStub {\n    Workers internal token;\n    uint256 internal reserves;\n    mapping(address => uint256) public credit;\n\n    function sync(Workers token_) external {\n        token = token_;\n        reserves = token_.balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        paid = token.balanceOf(address(this)) - reserves;\n        credit[msg.sender] += paid;\n    }\n\n    function take(address to, uint256 amount) external {\n        credit[msg.sender] -= amount;\n        token.transfer(to, amount);\n    }\n}\n\n/// @dev A registered, fee-aware venue (v2-style pair): it delivers swap output to whatever recipient the trader names.\ncontract PairStub {\n    function swap(Workers token, address to, uint256 amountOut) external {\n        token.transfer(to, amountOut);\n    }\n}\n\ncontract WorkersFeeBypassTest is Test {\n    uint256 internal constant SUPPLY = 1_000_000_000 ether;\n\n    FactoryStub internal factory;\n    PoolManagerStub internal manager;\n    PairStub internal pair;\n    Workers internal token;\n    address internal admin = makeAddr(\"admin\");\n    address internal treasury = makeAddr(\"treasury\");\n    address internal attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        factory = new FactoryStub();\n        manager = new PoolManagerStub();\n        pair = new PairStub();\n        token = factory.deploy(address(manager), 41, admin);\n        vm.startPrank(admin);\n        token.setFeeRecipient(treasury);\n        token.setTradeVenue(address(pair), true);\n        vm.stopPrank();\n        factory.send(token, address(pair), 1_000 ether);\n        factory.send(token, attacker, 1_000 ether);\n    }\n\n    /// @dev A direct buy from the venue pays the 2% fee. This is the behaviour the owner expects for every buy.\n    function test_DirectBuyFromVenuePaysFee() public {\n        vm.prank(attacker);\n        pair.swap(token, attacker, 100 ether);\n        assertEq(token.balanceOf(attacker), 1_098 ether);\n        assertEq(token.balanceOf(treasury), 2 ether);\n    }\n\n    /// @dev The same buy, with the venue's output routed through the PoolManager (sync -> pair pays the manager ->\n    /// settle -> take), pays nothing: venue -> manager is exempt (to == poolManager) and manager -> attacker is exempt\n    /// (msg.sender == poolManager). The attacker nets the full 100 WORK and the treasury gets 0.\n    function test_BuyRoutedThroughPoolManagerSkipsFee() public {\n        vm.startPrank(attacker);\n        manager.sync(token);\n        pair.swap(token, address(manager), 100 ether);\n        uint256 credited = manager.settle();\n        manager.take(attacker, credited);\n        vm.stopPrank();\n        assertEq(token.balanceOf(attacker), 1_098 ether, \"attacker received the gross amount, fee was skipped\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"treasury received no fee on a venue buy\");\n    }\n\n    /// @dev Selling into the venue via the PoolManager: the attacker pays the manager (exempt, to == poolManager), then\n    /// takes straight into the venue (exempt, msg.sender == poolManager). The venue receives 100 WORK instead of 98.\n    function test_SellRoutedThroughPoolManagerSkipsFee() public {\n        vm.startPrank(attacker);\n        manager.sync(token);\n        token.transfer(address(manager), 100 ether);\n        manager.settle();\n        manager.take(address(pair), 100 ether);\n        vm.stopPrank();\n        assertEq(token.balanceOf(address(pair)), 1_098 ether, \"venue received the gross amount, fee was skipped\");\n        assertEq(token.balanceOf(treasury), 2 ether, \"treasury received no fee on a venue sell\");\n    }\n}","reproduction":"State: owner has set feeRecipient = treasury and setTradeVenue(pair, true) where pair is a v2-style venue holding 1000 WORK; attacker holds 1000 WORK. Direct path for comparison: attacker calls pair.swap(to=attacker, 100e18) -> attacker +98e18, treasury +2e18 (fee charged). Bypass path (buy): attacker calls poolManager.sync(WORK); pair.swap(to=poolManager, 100e18) [venue->manager, exempt by `to == poolManager`]; poolManager.settle() credits 100e18; poolManager.take(attacker, 100e18) [manager->attacker, exempt by `msg.sender == poolManager`]. Expected: attacker 1098e18, treasury 2e18. Actual: attacker 1100e18, treasury 0. Bypass path (sell): poolManager.sync(WORK); WORK.transfer(poolManager, 100e18) [exempt]; settle(); take(pair, 100e18) [exempt]. Expected: pair 1098e18, treasury 2e18. Actual: pair 1100e18, treasury 0. Run: forge test --match-path test/scratch/WorkersFeeBypass.t.sol (2 of 3 tests fail on current code; all pass when the venue check takes precedence over PoolManager counterparty status).","severity":"medium","snippet":"        if (\n            from == address(0) || to == address(0) || msg.sender == launchFactory || msg.sender == poolManager\n                || from == poolManager || to == poolManager || (!isTradeVenue[from] && !isTradeVenue[to])\n        ) {","title":"Any trader bypasses the 2% venue fee by routing a venue trade through the exempt PoolManager (to/from == poolManager, msg.sender == poolManager)"},{"citation":"resolved","description":"DEPENDENCIES.md states the vendored library files are unmodified upstream release sources and tells the reader to verify them with `sha256sum --check DEPENDENCIES.sha256`. That command fails for seven forge-std files. The recorded hashes are the correct upstream v1.9.7 hashes (verified by fetching the files from the foundry-rs/forge-std v1.9.7 tag), so it is the committed copies that were altered. Diffing against upstream shows the changes are formatting only (`forge fmt` re-wrapping of long signatures in StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, interfaces/IERC7540.sol, interfaces/IMulticall3.sol), with no semantic change, and forge-std is test-only. All seven OpenZeppelin v5.0.2 files that the production contract compiles against are byte-identical to upstream. Impact: the provenance statement a reviewer or the launch operator is told to rely on does not hold; a reader running the documented check gets 7 failures and cannot tell from the docs whether the test harness was tampered with. Fix: either restore the upstream bytes for the seven files (and exclude lib/ from forge fmt), or regenerate DEPENDENCIES.sha256 from the committed files and amend the 'unmodified' claim to say the forge-std copies were reformatted.","line":10,"path":"DEPENDENCIES.md","reproduction":"Run `sha256sum --check DEPENDENCIES.sha256` in the repository root. Expected (per DEPENDENCIES.md): every line OK. Actual: lib/forge-std/src/StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, interfaces/IERC7540.sol, interfaces/IMulticall3.sol report FAILED; 'WARNING: 7 computed checksums did NOT match'. Example: recorded/upstream hash of Vm.sol is 9068805b59ac1d0e..., committed file hashes to a1b1c82924aecf0f.... `diff <(curl -sL https://raw.githubusercontent.com/foundry-rs/forge-std/v1.9.7/src/Vm.sol) lib/forge-std/src/Vm.sol` shows only line re-wrapping.","severity":"low","snippet":"The library files are unmodified upstream release sources. Their SHA-256 hashes are recorded in `DEPENDENCIES.sha256`; verify using `sha256sum --check DEPENDENCIES.sha256`. Foundry and solc binaries are not vendored: the verification environment provides the pinned compiler.","title":"DEPENDENCIES.md provenance claim is false: 7 vendored forge-std files do not match DEPENDENCIES.sha256 / upstream v1.9.7"},{"citation":"resolved","description":"The fourth constructor argument is the administration address. The manifest rules for this launch allow the token's constructorArgs to be static words or $factory, $poolManager, $launchNumber only; $requester is defined for application contracts, not for the token. The README's deployment table (README.md:26) nevertheless lists the source of initialOwner_ as `$requester`, and the same README warns that the factory must never be substituted into this slot. The contract does not enforce that warning: Ownable(initialOwner_) rejects only address(0), so a manifest that fills the slot with the factory address (the most plausible substitution when $requester does not resolve, and what the protected harness would deploy with) or the PoolManager deploys successfully. The resulting token has an owner that is a contract with no call-forwarding, renounceOwnership() is overridden to revert, and transferOwnership/acceptOwnership require the owner to call them. Consequence: setFeeRecipient and setTradeVenue are uncallable forever, the 'Trade fee 2%' feature can never be activated, and nothing in the launch floor detects it (supply, flows, decimals and opcode checks all pass). This is a one-line guard the constructor can add at zero cost: revert if initialOwner_ == factory_ || initialOwner_ == poolManager_. Separately, the README table should name the slot as a static address word, matching the manifest rule, instead of `$requester`.","line":46,"path":"src/Workers.sol","reproduction":"From the factory contract: `new Workers(address(this), poolManager, 41, address(this))` (initialOwner_ == factory_). Expected: deployment rejected, since the README states the factory must never be the owner and the fee could never be configured. Actual: deployment succeeds, owner() == factory, totalSupply == 1e27 minted to the factory. Afterwards every call to setFeeRecipient(x) / setTradeVenue(v, true) from any EOA reverts with OwnableUnauthorizedAccount, the factory has no function that calls them, renounceOwnership() reverts with OwnershipRenunciationDisabled, and transferOwnership(newOwner) is only callable by the factory. The fee is permanently off while the floor's checks all pass. Same outcome with initialOwner_ == poolManager_.","severity":"low","snippet":"    constructor(address factory_, address poolManager_, uint64 launchNumber_, address initialOwner_)\n        ERC20(\"Workers\", \"WORK\")\n        Ownable(initialOwner_)\n    {","title":"Constructor accepts the factory (or PoolManager) as initialOwner_, which permanently disables fee configuration; README also documents the owner slot as `$requester`, a placeholder the token manifest "}],"hash":"7eeed81c20c2a0915a9363748fb88f1edf3ba846a9afb5cd741f43c8590f93cd","nodeId":"54710774-768f-49e8-ad8d-de6070dc9cea","outcome":"completed","summary":"The review is complete. No tracked files were changed; the findings are in `.imd-findings.json` with a proof test under `test/scratch/`.\n\n**What I found**\n\n- **Medium: the 2% venue fee is bypassable by any trader.** In `_update` at `src/Workers.sol:100`, the PoolManager exemption (as caller, sender or receiver) is evaluated before the venue check. Uniswap v4's PoolManager is a permissionless router, so a trader buys from an owner-registered venue with the PoolManager as recipient, settles, and takes the tokens out. Both hops are exempt and the treasury gets nothing. Selling works in reverse via `take` into the venue. The attached proof test fails on the current code and passes on a patched copy where venue involvement takes precedence over PoolManager counterparty status. The launch flows stay exact under that fix because launch endpoints can never be venues, but it reverses the documented precedence, so it is the author's design call.\n- **Low: the dependency provenance claim is false.** DEPENDENCIES.md says the vendored sources are unmodified and verifiable with the recorded hashes. Seven forge-std files fail that check. The recorded hashes match upstream v1.9.7, so the committed copies were altered. The diff is formatting only, and all seven OpenZeppelin production files are byte-identical to upstream.\n- **Low: the constructor accepts the factory or PoolManager as owner.** Only the zero address is rejected. If the owner slot is filled with the factory, which the README itself warns against and which is the obvious substitution since the token manifest does not resolve `$requester`, fee configuration is uncallable forever and renunciation is disabled. A one-line guard prevents it.\n\n**Coverage**\n\nAll seven listed entry points have rows, plus the constructor, the distributor lookup, and four invariants. Transfer and transferFrom carry finding 1, the constructor carries finding 3, the vendored periphery carries finding 2, and the rest hold. Ownership, approvals, and the fee arithmetic (gross balance check, floor rounding, recipient aliasing, atomic reverts) all traced correctly, and the existing 40-test suite including the invariant run passes.\n\n**Not reached**\n\nNo launch manifest exists in the tree, so constructor inputs for the real deployment could not be checked. Live Uniswap v4 integration was modeled with a minimal sync/settle/take stub, not the real PoolManager.","treeHash":null,"usage":{"cachedInputTokens":1195371,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":31449,"runtime":"claude","turns":34,"wallClockMs":464821}},{"artifacts":[],"attempt":1,"bundleHash":"03f949b7e89686e0208fada0bd872f8dbd411897298965b219e5cee3d5a2fbbc","device":"fd7c32744e171d7d","findings":[],"hash":"84f29bbfbfa459a0eb021c32f35fdc280becb75a06fb61a7d4238ab6abf155be","nodeId":"b2354d9b-e40e-461a-b90f-c1a159167427","outcome":"completed","summary":"Updated only [launch.json](/home/imd1161/.identitymd/work/e16fbe31-a5c8-46af-a344-9a04ec62b450/b2354d9b-e40e-461a-b90f-c1a159167427/launch.json), clarifying when PoolManager transfers incur the 2% fee. All deployment values remain unchanged.\n\nValidated schema constraints, economics, supply, and constructor arguments. `forge build` succeeded; `forge test` passed all 53 tests.","treeHash":"0214591c6c78e4ac3953fc2cdcbdb20681535296","usage":{"cachedInputTokens":155520,"inputTokens":42111,"model":"gpt-6-astra","outputTokens":3461,"runtime":"codex","turns":3,"wallClockMs":99167}},{"artifacts":[],"attempt":1,"bundleHash":"436e8883ca9099a1f700cdf3c664dfe996229d47209fc7b4c55c4fb7fad9afc9","device":"d328e90a3d7134f5","findings":[],"hash":"8b2c324cc800667580f72b8c8f6932f473cdfb37486ff664e078fd58e977b56a","nodeId":"112277a7-0557-4473-a3fc-9ab7596a7826","outcome":"completed","summary":"Updated two test files for the revised venue/fee-recipient restrictions, preserving failure coverage and strengthening stateful invariants.\n\nValidation passed:\n- Offline build\n- All 71 tests with two seeds\n- Second run with increased fuzz cases\n- Formatting and diff checks","treeHash":"52eb95749511172f238de7c4677c088a4dde3dee","usage":{"cachedInputTokens":1055616,"inputTokens":89783,"model":"gpt-6-astra","outputTokens":4567,"runtime":"codex","turns":5,"wallClockMs":168018}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e12f98dda6acc55f","findings":[{"citation":"resolved","description":"Second-round settlement of id 7ca78f4556d46b7369de84929c9cd9303baa93db130d572508918ea2640aa67e. The author disputes treating this as a defect and I accept that: the launch floor requires trader<->PoolManager swaps to settle exactly, so a transfer tax cannot apply on the native v4 pool, and collecting there would need a v4 hook delivered as an application contract, which is a scope decision for the requester rather than a defect in this token. The README (lines 5 and 36-37) and launch.json notes now state plainly that the native pool and every other v4 pool on the same manager are untaxed for trades with unregistered accounts and that fees apply only on owner-registered secondary venues, and (after the relay fix) on venue<->manager legs. Kept at info only so the requester sees the delivered economics; it does not reopen the work.","line":78,"path":"src/Workers.sol","reproduction":"Owner calls setFeeRecipient(treasury) then setTradeVenue(poolManager, true): reverts InvalidTradeVenue(poolManager) at src/Workers.sol:80. Factory seeds (msg.sender == launchFactory, exempt at line 106). Trader buys 100e18 from the pool: manager -> trader has neither endpoint registered, exempt at line 107, treasury stays 0; trader sells 100e18 back: trader -> manager, same, treasury 0. Confirmed by the project's test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive (treasury == 0 after a manager buy and sell with fees active) and by my scratch test test/scratch/Settle.t.sol::test_fixedPrecedence (manager->alice and alice->manager exact, treasury 0; manager->venue and venue->manager each pay 2e18).","severity":"info","snippet":"                venue.code.length == 0 || venue == address(this) || venue == launchFactory || venue == poolManager","title":"By construction the 2% fee cannot apply to the launch pool or any Uniswap v4 pool; at launch the fee collects nothing (settled: author's dispute accepted, documented scope limitation, nothing to fix)"},{"citation":"resolved","description":"Documentation only; no code impact. README line 43 says no paired currency, pool economics or production addresses were supplied and that the project contains no launch manifest, but launch.json is committed at the repository root with pairedCurrency 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127, fee 3000, tickSpacing 60, initialPrice, economics {poolBps 8800, initialMarketCapWei 2.5e21, remainderTo 0xbb85c1b7540d9e7b56f7a595fc1ff7dd07cb0823} and constructorArgs[3] equal to remainderTo. A reader who trusts the README will not look for the manifest, and a manifest reviewer comparing the two sees a contradiction. The manifest itself checks out: constructorArgs[3] == economics.remainderTo, totalSupply equals INITIAL_SUPPLY, decimals 18, and initialPrice == floor(sqrt(2.5e21 * 2^192 / 1e27)) == 125270724187523965593206900. Fix: drop or reword the sentence to point at launch.json.","line":43,"path":"README.md","reproduction":"sed -n 43p README.md prints the quoted sentence; cat launch.json shows kind custom_token with pool and economics populated and constructorArgs[3] = 0xbb85c1b7540d9e7b56f7a595fc1ff7dd07cb0823. Expected: README and manifest agree. Actual: README denies the manifest exists.","severity":"info","snippet":"No chain, launch number, paired currency, pool economics, or production addresses were supplied with this assignment. This project contains no fabricated launch manifest and performs no deployment, broadcasting, bridging, or key access.","title":"README states no launch manifest or pool economics exist while launch.json is committed with them (stale sentence from before the manifest was added)"}],"hash":"d2157ddcd00e51c766277bd4499ea0c9ba5514082e2ed934c472a57a691e1ef8","nodeId":"be2b4be2-a052-49dc-9cd7-ed53650f2513","outcome":"completed","summary":"Review complete. `.imd-findings.json` is written; the tree is otherwise unchanged (scratch tests removed).\n\n## Settlement of the previous round\n\n| Earlier finding | Verdict |\n|---|---|\n| **[medium] PoolManager relay bypass** (f62f3fa45413) | **Fixed.** `_update` at `src/Workers.sol:105-108` now exempts only mint/burn, factory-caller, and transfers where neither endpoint is a venue (plus the distributor branch). The attached proof, copied to `test/scratch/`, passes 3/3 on the revised code. My own scratch test confirmed manager→venue and venue→manager each pay 2e18 while manager↔user, factory seed and distributor claims stay exact. Permanent regressions were added in `test/WorkersRelay.t.sol`. |\n| **[low] setFeeRecipient accepts protected endpoints** (9cb6d3a7) | **Fixed.** Lines 63-66 reject factory, PoolManager, registered venues and the distributor; line 79 closes the reverse order (recipient can't become a venue). Reproduced all rejections. |\n| **[low] Constructor accepts factory/PoolManager as owner** (61c9ebc1) | **Fixed.** Line 54 reverts `OwnableInvalidOwner`; README names the fourth word a literal address; `launch.json` `constructorArgs[3] == economics.remainderTo`. |\n| **[low] DEPENDENCIES provenance claim** (96b70e1f) | **Fixed.** `sha256sum --check` → 38/38 OK; DEPENDENCIES.md names the seven reformatted forge-std files and states hashes are of vendored bytes. |\n| **[info] Fee can't apply to the launch pool** (7ca78f45) | **Dispute accepted.** Scope limitation, now clearly documented; kept at info, nothing to fix. |\n\nFull suite: 71/71 (including both invariant suites). Protected-harness flows all land in exempt branches since no venue can be the factory, manager or distributor.\n\n## New this round\n\nOne info-level documentation inconsistency only: `README.md:43` still says no manifest/pool economics exist while `launch.json` is committed with them (manifest itself checks out — `initialPrice` matches `floor(sqrt(2.5e21·2^192/1e27))` exactly). Nothing blocking; no critical/high findings, so no proofs attached.\n\nCoverage: all 7 entry points `holds`, plus constructor, four settled rows and two invariants.","treeHash":null,"usage":{"cachedInputTokens":774111,"inputTokens":24,"model":"claude-fable-5-1","outputTokens":13204,"runtime":"claude","turns":12,"wallClockMs":183886}},{"artifacts":[],"attempt":1,"bundleHash":"0920557d9dc96877e0e6b92bfe8981edc06f8c7894ce144e836e0380f33b03d3","device":"eedfe508061c8582","findings":[],"hash":"f05fc461121492e28c45f23d36ad7f7121d89f33343d25187b524496f4b5b909","nodeId":"eb6bfc84-3928-4dc3-a08f-c3fb894bd902","outcome":"completed","summary":"Implemented Workers (WORK): 1 billion tokens, 18 decimals, constructor-only mint, and a fixed 2% fee for configured trading venues.\n\n`forge build`, all 40 tests, and `forge fmt --check` pass.\n\nThe owner must configure the fee recipient and venues after deployment. Required launch flows remain exempt. Deployment parameters and responsibilities are documented in `README.md`.","treeHash":"af9271aa12ea1ff628d938ba9e11bfbe0c5fc9aa","usage":{"cachedInputTokens":438656,"inputTokens":67098,"model":"gpt-6-astra","outputTokens":20469,"runtime":"codex","turns":5,"wallClockMs":508407}}],"verification":[{"checks":[{"durationMs":1676,"exitCode":0,"name":"build","output":"Compiling 33 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.58s\nCompiler run successful!\n","passed":true},{"durationMs":1054,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/WorkersRelay.t.sol:WorkersRelayTest\n[PASS] test_RelayedBuySettlesNetAmountAndPaysVenueFee() (gas: 301236)\n[PASS] test_RelayedSellPaysVenueFeeAndConsumesGrossAllowance() (gas: 365699)\n[PASS] test_RelayedSellWithoutGrossAllowanceRevertsAtomically() (gas: 255727)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 19.16ms (611.67µs CPU time)\n\nRan 9 tests for test/WorkersConfiguration.t.sol:WorkersConfigurationTest\n[PASS] test_ConstructorRejectsFactoryAsOwner() (gas: 36519)\n[PASS] test_ConstructorRejectsPoolManagerAsOwner() (gas: 36489)\n[PASS] test_ExistingFeeRecipientCannotBecomeVenue() (gas: 99192)\n[PASS] test_FeeRecipientRejectsDistributor() (gas: 107830)\n[PASS] test_FeeRecipientRejectsFactory() (gas: 50768)\n[PASS] test_FeeRecipientRejectsPoolManager() (gas: 50761)\n[PASS] test_FeeRecipientRejectsRegisteredVenue() (gas: 236828)\n[PASS] test_FormerRecipientCanBecomeVenueAfterTreasuryChanges() (gas: 335262)\n[PASS] test_RecipientUpdateRequiresWorkingRegistryAndPreservesPreviousSettingOnFailure() (gas: 290876)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 20.21ms (1.60ms CPU time)\n\nRan 12 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 499697)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 419861)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 267748)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 373901)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 349706)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1971918)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 403449)\n[PASS] test_PoolManagerTransferFromSpendsGrossAllowanceAndPaysVenueFee() (gas: 423224)\n[PASS] test_PoolManagerTransfersToAndFromRegisteredVenuesPayFee() (gas: 477224)\n[PASS] test_RegistryFailureRevertsVenueTransfersButAllowsPlainSettlement() (gas: 640731)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2226775)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 397764)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 20.87ms (8.05ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 394517, ~: 397808)\nLogs:\n  Bound result 600919880866413388747071734\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 446416, ~: 450026)\nLogs:\n  Bound result 18\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 390073)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 559936)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 192672)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 386642)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 459099)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 410691)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 375326)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 393095)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 583837)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 428898)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 587792)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 453790)\n[PASS] test_SellPaysTwoPercent() (gas: 370526)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 437201)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171127)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 369200)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 531453)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 360946)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 22.52ms (50.76ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2762  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2689  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2741  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2825\n  Bound result 38044194782594446593607763\n  Bound result 10000\n  Bound result 41\n  Bound result 244\n  Bound result 12\n  Bound result 24576\n  Bound result 244\n  Bound result 10000\n  Bound result 8702\n  Bound result 49\n  Bound result 255\n  Bound result 244\n  Bound result 4317\n  Bound result 66593208915057681156183587\n  Bound result 296000000000000000000\n  Bound result 204\n  Bound result 296000000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 981.37ms (979.76ms CPU time)\n\nRan 5 test suites in 982.63ms (1.06s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":12,\"DEPENDENCIES.sha256\":38,\"README.md\":70,\"foundry.toml\":21,\"remappings.txt\":2,\"src/Workers.sol\":134,\"test/Workers.t.sol\":374,\"test/WorkersConfiguration.t.sol\":80,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":122,\"test/WorkersLaunch.t.sol\":186,\"test/WorkersRelay.t.sol\":140},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":505,"exitCode":0,"name":"slither","output":"slither: no results at low impact or above","passed":true},{"durationMs":289,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/Workers.sol:15: Centralization Risk (4 places)\n[low] large-numeric-literal at src/Workers.sol:16: Large Numeric Literal (2 places)","passed":true},{"durationMs":1184,"exitCode":0,"name":"proof f62f3fa45413","output":"Compiling 29 files with Solc 0.8.26\nSolc 0.8.26 finished in 705.16ms\nCompiler run successful!\n\nRan 3 tests for test/imd-proof-332d901e/Proof_f62f3fa45413.t.sol:PoolManagerRelayBypassTest\n[PASS] test_buyRoutedThroughPoolManagerStillPaysFee() (gas: 268239)\n[PASS] test_directSellPaysFee() (gas: 177636)\n[PASS] test_sellRoutedThroughPoolManagerStillPaysFee() (gas: 317875)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.02ms (612.19µs CPU time)\n\nRan 1 test suite in 2.63ms (1.02ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0a01891f47293647bf8fe8603ae3e837c2ae63a6c4dfabfa5cf39e784a38fc83","verifiedTreeHash":"b15efa1f22cf1fdab1ebc60fb13c9cf4fd97bf63","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":1796,"exitCode":0,"name":"build","output":"Compiling 33 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.71s\nCompiler run successful!\n","passed":true},{"durationMs":4062,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 14 tests for test/WorkersAdversarial.t.sol:WorkersAdversarialTest\n[PASS] testFuzz_ApprovalRevocationAndReuseCannotExceedGrossBudget(uint256,uint256) (runs: 1000, μ: 480274, ~: 487643)\nLogs:\n  Bound result 1121432165\n  Bound result 201\n\n[PASS] testFuzz_FeeHasLessThanOneMinorUnitRoundingError(uint256) (runs: 1000, μ: 203098, ~: 206424)\nLogs:\n  Bound result 2\n\n[PASS] testFuzz_RepeatedRoundTripsOnlyLoseTheFeesPaid(uint256,uint256) (runs: 1000, μ: 1132813, ~: 1220002)\nLogs:\n  Bound result 879565169967685518122646857\n  Bound result 1\n\n[PASS] test_DelegatedSelfTransferWithAllRolesAliasedStillConsumesGrossAllowance() (gas: 268340)\n[PASS] test_FactoryAndDistributorDelegatedExemptionsDeliverGrossAndExhaustApproval() (gas: 440388)\n[PASS] test_FullSupplyCanBeBoughtWithFiniteAllowance() (gas: 271333)\n[PASS] test_FullSupplyCanBeSoldInOneTransfer() (gas: 207496)\n[PASS] test_InvalidTransferFromDestinationRestoresSpentAllowance() (gas: 190097)\n[PASS] test_MaximumGrossAmountRevertsBeforeFeeArithmetic() (gas: 252773)\n[PASS] test_OneMinorUnitTradeSucceedsWithoutRoundingUpTheFee() (gas: 162955)\n[PASS] test_RegistryFailureCannotPartiallyEnableVenueAndOwnerCanDisableExistingOne() (gas: 399004)\n[PASS] test_RegistryFailureRestoresFiniteAllowanceAndRetrySucceeds() (gas: 348025)\n[PASS] test_ReplacedPendingOwnerCannotAcceptOrConfigure() (gas: 312590)\n[PASS] test_ZeroDelegatedTradeNeedsNoAllowanceAndMovesNoValue() (gas: 160640)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 98.02ms (290.66ms CPU time)\n\nRan 11 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 491855)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 411695)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 259516)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 365985)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 350054)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1964651)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 395388)\n[PASS] test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount() (gas: 375733)\n[PASS] test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers() (gas: 487176)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2183743)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 506221)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 98.03ms (7.93ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 386573, ~: 389758)\nLogs:\n  Bound result 1357\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 438611, ~: 441964)\nLogs:\n  Bound result 325600468850253224679047772\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 382047)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 544316)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 184441)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 369592)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 443357)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 402665)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 367127)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 385069)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 567871)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 420408)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 580285)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 445988)\n[PASS] test_SellPaysTwoPercent() (gas: 362500)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 429175)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171351)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 361174)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 523600)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 352747)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 98.07ms (202.54ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2742  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2752  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2698  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 30722941281013782156495795\n  Bound result 4269\n  Bound result 2696\n  Bound result 268\n  Bound result 2047\n  Bound result 6356\n  Bound result 200\n  Bound result 6048\n  Bound result 99999999999999999999961569\n  Bound result 19975\n  Bound result 200000000000000000000\n  Bound result 96\n  Bound result 95\n  Bound result 5\n  Bound result 3394977262\n  Bound result 6\n  Bound result 99000000000000000000\n  Bound result 1198418961580428\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 951.98ms (950.71ms CPU time)\n\nRan 2 tests for test/WorkersAllowanceInvariant.t.sol:WorkersAllowanceInvariantTest\n[PASS] invariant_BalancesAllowancesAndAuthorityMatchCumulativeAccounting() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| WorkersAllowanceHandler | approve                   | 2858  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | configure                 | 2728  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | move                      | 2710  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | rejectTransfer            | 2717  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | spend                     | 2661  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | unauthorizedConfiguration | 2710  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 377799\n  Bound result 0\n  Bound result 10806\n  Bound result 0\n  Bound result 188059205621831855350546342\n  Bound result 20\n  Bound result 586\n  Bound result 197\n  Bound result 24576\n  Bound result 3654\n  Bound result 0\n  Bound result 8092\n  Bound result 3358\n  Bound result 255\n  Bound result 200000000000000000000\n  Bound result 12106\n  Bound result 49\n\n[PASS] test_HandlerSequenceExercisesApprovalAndFailureTransitions() (gas: 2451202)\nLogs:\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50\n  Bound result 100\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.99s (3.99s CPU time)\n\nRan 5 test suites in 3.99s (5.24s CPU time): 56 tests passed, 0 failed, 0 skipped (56 total tests)\n","passed":true},{"durationMs":31,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":10,\"DEPENDENCIES.sha256\":38,\"README.md\":69,\"foundry.toml\":21,\"remappings.txt\":2,\"src/Workers.sol\":128,\"test/Workers.t.sol\":375,\"test/WorkersAdversarial.t.sol\":261,\"test/WorkersAllowanceInvariant.t.sol\":222,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":109,\"test/WorkersLaunch.t.sol\":171},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1b6b54c92464d9d624f58aba904cf20badddadcfb95e4bae795d8173764eece1","verifiedTreeHash":"82417d4f9cb7ca7145fca54e9ead9c742f76666d","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":1450,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.36s\nCompiler run successful!\n","passed":true},{"durationMs":1034,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 491855)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 411695)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 259516)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 365985)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 350054)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1964651)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 395388)\n[PASS] test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount() (gas: 375733)\n[PASS] test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers() (gas: 487176)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2183743)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 506221)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 18.56ms (6.70ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 386323, ~: 389782)\nLogs:\n  Bound result 24576\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 438153, ~: 442000)\nLogs:\n  Bound result 24576\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 382047)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 544316)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 184441)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 369592)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 443357)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 402665)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 367127)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 385069)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 567871)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 420408)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 580285)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 445988)\n[PASS] test_SellPaysTwoPercent() (gas: 362500)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 429175)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171351)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 361174)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 523600)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 352747)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 18.87ms (43.03ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2789  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2627  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2776  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 99999999999999999999999991\n  Bound result 6\n  Bound result 656\n  Bound result 2986\n  Bound result 3\n  Bound result 193\n  Bound result 10\n  Bound result 12\n  Bound result 10195540764136877542493524\n  Bound result 725\n  Bound result 34\n  Bound result 96\n  Bound result 7561\n  Bound result 2000000000000000000\n  Bound result 6083\n  Bound result 6\n  Bound result 73254633247464499331788975\n  Bound result 6571\n  Bound result 203\n  Bound result 4\n  Bound result 3359\n  Bound result 99999997999999999999994116\n  Bound result 18446744073709551616\n  Bound result 10\n  Bound result 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 967.33ms (965.66ms CPU time)\n\nRan 3 test suites in 968.48ms (1.00s CPU time): 40 tests passed, 0 failed, 0 skipped (40 total tests)\n","passed":true},{"durationMs":31,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":10,\"DEPENDENCIES.sha256\":38,\"README.md\":69,\"foundry.toml\":21,\"launch.json\":25,\"remappings.txt\":2,\"src/Workers.sol\":128,\"test/Workers.t.sol\":375,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":109,\"test/WorkersLaunch.t.sol\":171},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4401d3272058ac44cddf58715437018f05fa8e15db881821c87acb1a4c8c4584","verifiedTreeHash":"46c693c2668600ed76ff53104319ea64a94a7db4","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":1576,"exitCode":0,"name":"build","output":"Compiling 33 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.49s\nCompiler run successful!\n","passed":true},{"durationMs":1011,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/WorkersRelay.t.sol:WorkersRelayTest\n[PASS] test_RelayedBuySettlesNetAmountAndPaysVenueFee() (gas: 301236)\n[PASS] test_RelayedSellPaysVenueFeeAndConsumesGrossAllowance() (gas: 365699)\n[PASS] test_RelayedSellWithoutGrossAllowanceRevertsAtomically() (gas: 255727)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.23ms (498.88µs CPU time)\n\nRan 9 tests for test/WorkersConfiguration.t.sol:WorkersConfigurationTest\n[PASS] test_ConstructorRejectsFactoryAsOwner() (gas: 36519)\n[PASS] test_ConstructorRejectsPoolManagerAsOwner() (gas: 36489)\n[PASS] test_ExistingFeeRecipientCannotBecomeVenue() (gas: 99192)\n[PASS] test_FeeRecipientRejectsDistributor() (gas: 107830)\n[PASS] test_FeeRecipientRejectsFactory() (gas: 50768)\n[PASS] test_FeeRecipientRejectsPoolManager() (gas: 50761)\n[PASS] test_FeeRecipientRejectsRegisteredVenue() (gas: 236828)\n[PASS] test_FormerRecipientCanBecomeVenueAfterTreasuryChanges() (gas: 335262)\n[PASS] test_RecipientUpdateRequiresWorkingRegistryAndPreservesPreviousSettingOnFailure() (gas: 290876)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.24ms (1.52ms CPU time)\n\nRan 12 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 499697)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 419861)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 267748)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 373901)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 349706)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1971918)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 403449)\n[PASS] test_PoolManagerTransferFromSpendsGrossAllowanceAndPaysVenueFee() (gas: 423224)\n[PASS] test_PoolManagerTransfersToAndFromRegisteredVenuesPayFee() (gas: 477224)\n[PASS] test_RegistryFailureRevertsVenueTransfersButAllowsPlainSettlement() (gas: 640731)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2226775)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 397764)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 22.12ms (6.56ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 394301, ~: 397784)\nLogs:\n  Bound result 30802\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 446060, ~: 449990)\nLogs:\n  Bound result 30802\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 390073)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 559936)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 192672)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 386642)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 459099)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 410691)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 375326)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 393095)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 583837)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 428898)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 587792)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 453790)\n[PASS] test_SellPaysTwoPercent() (gas: 370526)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 437201)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171127)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 369200)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 531453)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 360946)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 22.24ms (48.28ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2735  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2756  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2701  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6\n  Bound result 99000000000000000000\n  Bound result 19228892389533828203876515\n  Bound result 8272\n  Bound result 95334411249101654417751365\n  Bound result 275\n  Bound result 3\n  Bound result 6\n  Bound result 98\n  Bound result 24433226788878791698580849\n  Bound result 3960000000000000000\n  Bound result 2193\n  Bound result 6131\n  Bound result 233315549323424859343156\n  Bound result 105204433399344963494696171\n  Bound result 2019\n  Bound result 172732264759282209876963706\n  Bound result 1005668090976935198\n  Bound result 12\n  Bound result 2771\n  Bound result 14220819550846006358173820\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 941.65ms (940.17ms CPU time)\n\nRan 5 test suites in 943.04ms (994.48ms CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":33,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":12,\"DEPENDENCIES.sha256\":38,\"README.md\":70,\"foundry.toml\":21,\"launch.json\":25,\"remappings.txt\":2,\"src/Workers.sol\":134,\"test/Workers.t.sol\":374,\"test/WorkersConfiguration.t.sol\":80,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":122,\"test/WorkersLaunch.t.sol\":186,\"test/WorkersRelay.t.sol\":140},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"84f29bbfbfa459a0eb021c32f35fdc280becb75a06fb61a7d4238ab6abf155be","verifiedTreeHash":"0214591c6c78e4ac3953fc2cdcbdb20681535296","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2143,"exitCode":0,"name":"build","output":"Compiling 35 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.05s\nCompiler run successful!\n","passed":true},{"durationMs":4155,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/WorkersRelay.t.sol:WorkersRelayTest\n[PASS] test_RelayedBuySettlesNetAmountAndPaysVenueFee() (gas: 301236)\n[PASS] test_RelayedSellPaysVenueFeeAndConsumesGrossAllowance() (gas: 365699)\n[PASS] test_RelayedSellWithoutGrossAllowanceRevertsAtomically() (gas: 255727)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.64ms (630.52µs CPU time)\n\nRan 12 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 499697)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 419861)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 267748)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 373901)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 349706)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1971918)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 403449)\n[PASS] test_PoolManagerTransferFromSpendsGrossAllowanceAndPaysVenueFee() (gas: 423224)\n[PASS] test_PoolManagerTransfersToAndFromRegisteredVenuesPayFee() (gas: 477224)\n[PASS] test_RegistryFailureRevertsVenueTransfersButAllowsPlainSettlement() (gas: 640731)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2226775)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 397764)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 84.44ms (7.72ms CPU time)\n\nRan 9 tests for test/WorkersConfiguration.t.sol:WorkersConfigurationTest\n[PASS] test_ConstructorRejectsFactoryAsOwner() (gas: 36519)\n[PASS] test_ConstructorRejectsPoolManagerAsOwner() (gas: 36489)\n[PASS] test_ExistingFeeRecipientCannotBecomeVenue() (gas: 99192)\n[PASS] test_FeeRecipientRejectsDistributor() (gas: 107830)\n[PASS] test_FeeRecipientRejectsFactory() (gas: 50768)\n[PASS] test_FeeRecipientRejectsPoolManager() (gas: 50761)\n[PASS] test_FeeRecipientRejectsRegisteredVenue() (gas: 236828)\n[PASS] test_FormerRecipientCanBecomeVenueAfterTreasuryChanges() (gas: 335262)\n[PASS] test_RecipientUpdateRequiresWorkingRegistryAndPreservesPreviousSettingOnFailure() (gas: 290876)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 94.24ms (1.74ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 393541, ~: 397808)\nLogs:\n  Bound result 999999999999999999999999998\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 445234, ~: 450026)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 390073)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 559936)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 192672)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 386642)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 459099)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 410691)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 375326)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 393095)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 583837)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 428898)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 587792)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 453790)\n[PASS] test_SellPaysTwoPercent() (gas: 370526)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 437201)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171127)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 369200)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 531453)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 360946)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 98.59ms (197.10ms CPU time)\n\nRan 15 tests for test/WorkersAdversarial.t.sol:WorkersAdversarialTest\n[PASS] testFuzz_ApprovalRevocationAndReuseCannotExceedGrossBudget(uint256,uint256) (runs: 1000, μ: 481355, ~: 487224)\nLogs:\n  Bound result 831333991903028694321744351\n  Bound result 54030368964043547064352110\n\n[PASS] testFuzz_FeeHasLessThanOneMinorUnitRoundingError(uint256) (runs: 1000, μ: 202099, ~: 206180)\nLogs:\n  Bound result 7118497653762\n\n[PASS] testFuzz_RepeatedRoundTripsOnlyLoseTheFeesPaid(uint256,uint256) (runs: 1000, μ: 1099719, ~: 1103547)\nLogs:\n  Bound result 831333991903028694321744351\n  Bound result 3\n\n[PASS] test_DelegatedTreasurySelfTransferStillConsumesGrossAllowance() (gas: 218996)\n[PASS] test_FactoryAndDistributorDelegatedExemptionsDeliverGrossAndExhaustApproval() (gas: 440179)\n[PASS] test_FullSupplyCanBeBoughtWithFiniteAllowance() (gas: 271087)\n[PASS] test_FullSupplyCanBeSoldInOneTransfer() (gas: 207272)\n[PASS] test_InvalidTransferFromDestinationRestoresSpentAllowance() (gas: 190046)\n[PASS] test_MaximumGrossAmountRevertsBeforeFeeArithmetic() (gas: 252376)\n[PASS] test_OneMinorUnitTradeSucceedsWithoutRoundingUpTheFee() (gas: 162709)\n[PASS] test_RegistryFailureCannotPartiallyEnableVenueAndOwnerCanDisableExistingOne() (gas: 398892)\n[PASS] test_RegistryFailureRestoresFiniteAllowanceAndRetrySucceeds() (gas: 347608)\n[PASS] test_RejectedVenueRecipientUpdatePreservesFeeOnDelegatedSelfTransfer() (gas: 337774)\n[PASS] test_ReplacedPendingOwnerCannotAcceptOrConfigure() (gas: 312590)\n[PASS] test_ZeroDelegatedTradeNeedsNoAllowanceAndMovesNoValue() (gas: 160439)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 104.39ms (291.88ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2623  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2786  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2783  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1276\n  Bound result 47873718972015727908450433\n  Bound result 0\n  Bound result 3\n  Bound result 95\n  Bound result 60549925294016405485205202\n  Bound result 5163\n  Bound result 4305\n  Bound result 3994\n  Bound result 5468\n  Bound result 61111622075129853972557501\n  Bound result 2672\n  Bound result 570\n  Bound result 24576\n  Bound result 20000000000000000000000000\n  Bound result 1\n  Bound result 2008\n  Bound result 20040525950033642155388113\n  Bound result 24576\n  Bound result 10866324761425253311401398\n  Bound result 49\n  Bound result 5970\n  Bound result 98\n  Bound result 101\n  Bound result 1414\n  Bound result 50\n  Bound result 200\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 999.23ms (997.83ms CPU time)\n\nRan 3 tests for test/WorkersAllowanceInvariant.t.sol:WorkersAllowanceInvariantTest\n[PASS] invariant_BalancesAllowancesAndAuthorityMatchCumulativeAccounting() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| WorkersAllowanceHandler | approve                   | 2709  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | configure                 | 2770  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | move                      | 2717  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | rejectTransfer            | 2680  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | spend                     | 2784  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| WorkersAllowanceHandler | unauthorizedConfiguration | 2724  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 2110234842\n  Bound result 127\n  Bound result 12\n  Bound result 0\n  Bound result 27872114749933\n  Bound result 0\n  Bound result 9896\n  Bound result 3960000000000000000\n  Bound result 2749\n  Bound result 0\n  Bound result 6\n  Bound result 242\n\n[PASS] test_HandlerExercisesVenueRecipientConflictsAndRecovery() (gas: 1901718)\nLogs:\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n\n[PASS] test_HandlerSequenceExercisesApprovalAndFailureTransitions() (gas: 2475143)\nLogs:\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50000000000000000000000000\n  Bound result 50\n  Bound result 100\n\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.07s (4.08s CPU time)\n\nRan 7 test suites in 4.08s (5.46s CPU time): 71 tests passed, 0 failed, 0 skipped (71 total tests)\n","passed":true},{"durationMs":33,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":12,\"DEPENDENCIES.sha256\":38,\"README.md\":70,\"foundry.toml\":21,\"remappings.txt\":2,\"src/Workers.sol\":134,\"test/Workers.t.sol\":374,\"test/WorkersAdversarial.t.sol\":282,\"test/WorkersAllowanceInvariant.t.sol\":259,\"test/WorkersConfiguration.t.sol\":80,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":122,\"test/WorkersLaunch.t.sol\":186,\"test/WorkersRelay.t.sol\":140},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8b2c324cc800667580f72b8c8f6932f473cdfb37486ff664e078fd58e977b56a","verifiedTreeHash":"52eb95749511172f238de7c4677c088a4dde3dee","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":1263,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.18s\nCompiler run successful!\n","passed":true},{"durationMs":997,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/WorkersLaunch.t.sol:WorkersLaunchTest\n[PASS] test_DistributorResolvedAfterDeploymentAndClaimsStayExact() (gas: 491855)\n[PASS] test_ExemptAndPrivilegedCallersStillNeedHolderAllowance() (gas: 411695)\n[PASS] test_FactoryTransfersAreExactEvenToRegisteredVenues() (gas: 259516)\n[PASS] test_LateDistributorRegistrationOverridesEarlierVenueClassification() (gas: 365985)\n[PASS] test_LaunchDistributionWorksBeforeFeeConfiguration() (gas: 350054)\n[PASS] test_NoExternalMintBurnFreezeSeizureOrUpgradePowers() (gas: 1964651)\n[PASS] test_OtherLaunchDistributorIsNotExempt() (gas: 395388)\n[PASS] test_PoolManagerTransferFromSpendsFullAllowanceAndDeliversFullAmount() (gas: 375733)\n[PASS] test_RegistryFailureHasClearErrorWithoutAffectingPlainOrManagerTransfers() (gas: 487176)\n[PASS] test_RuntimeContainsNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 2183743)\n[PASS] test_SeedAndPoolManagerBuySellSettlementAreExactWithFeesActive() (gas: 506221)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 22.90ms (6.86ms CPU time)\n\nRan 28 tests for test/Workers.t.sol:WorkersTest\n[PASS] testFuzz_BuyConservesSupplyAndChargesExactlyTwoPercent(uint256) (runs: 512, μ: 385589, ~: 389782)\nLogs:\n  Bound result 264\n\n[PASS] testFuzz_SellUsingAllowanceMatchesDirectFee(uint256) (runs: 512, μ: 437348, ~: 442000)\nLogs:\n  Bound result 207178897658756277162207810\n\n[PASS] test_BuyPaysTwoPercentAndEmitsNetAndFeeTransfers() (gas: 382047)\n[PASS] test_ChangingRecipientAndRemovingVenueAffectsOnlyFutureTransfers() (gas: 544316)\n[PASS] test_ConfigurationEmitsEventsAndCannotUnsetRecipient() (gas: 184441)\n[PASS] test_ConstructorEmitsFullSupplyMint() (gas: 40479)\n[PASS] test_ConstructorMintsExactlyOnceToDeployer() (gas: 149733)\n[PASS] test_ConstructorRejectsFactoryWithoutRegistryContract() (gas: 11331)\n[PASS] test_ConstructorRejectsUnsetAndNonContractPoolManager() (gas: 94998)\n[PASS] test_ConstructorRejectsUnsetOwner() (gas: 36143)\n[PASS] test_ConstructorRejectsWrongFactory() (gas: 10838)\n[PASS] test_FeeRecipientAsReceiverReceivesFeeAndNetWithoutDoubleTax() (gas: 369592)\n[PASS] test_FeeRecipientAsSenderStillRequiresFullGrossBalance() (gas: 443357)\n[PASS] test_InfiniteAllowanceIsPreserved() (gas: 402665)\n[PASS] test_InsufficientAllowanceRevertsAtomically() (gas: 367127)\n[PASS] test_InsufficientBalanceRestoresAllowanceAndFees() (gas: 385069)\n[PASS] test_OnlyOwnerCanConfigureFeesAndVenues() (gas: 91154)\n[PASS] test_OwnerTransfersInTwoStepsWithoutChangingBalancesOrExemptions() (gas: 567871)\n[PASS] test_ProtectedAndInvalidEndpointsCannotBecomeVenues() (gas: 420408)\n[PASS] test_RenunciationIsDisabledAndOwnershipTransferCanBeCancelled() (gas: 147351)\n[PASS] test_RoundingAtSmallestFeeUnitAndZeroTransfers() (gas: 580285)\n[PASS] test_SelfTransfersPreserveSupplyAndPayFeeOnlyForVenues() (gas: 445988)\n[PASS] test_SellPaysTwoPercent() (gas: 362500)\n[PASS] test_TransferFromChargesFeeAndSpendsGrossAllowance() (gas: 429175)\n[PASS] test_UnconfiguredWalletTransfersWorkAndVenueActivationFailsClearly() (gas: 171351)\n[PASS] test_VenueToVenuePaysOnlyOneFee() (gas: 361174)\n[PASS] test_WalletAndUnregisteredContractTransfersAreUntaxed() (gas: 523600)\n[PASS] test_ZeroAddressRevertsWithoutBurningOrChargingFee() (gas: 352747)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 23.09ms (53.01ms CPU time)\n\nRan 1 test for test/WorkersInvariant.t.sol:WorkersInvariantTest\n[PASS] invariant_SupplyAndEveryTokenAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| WorkersHandler | changeRecipient | 2719  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | changeVenue     | 2734  | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| WorkersHandler | move            | 2739  | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6536\n  Bound result 298\n  Bound result 197\n  Bound result 244\n  Bound result 2293517445\n  Bound result 1000000000\n  Bound result 1\n  Bound result 1249\n  Bound result 24576\n  Bound result 61488191865008317\n  Bound result 129\n  Bound result 2439649222\n  Bound result 2\n  Bound result 18\n  Bound result 7475\n  Bound result 18\n  Bound result 3534\n  Bound result 48629035777154594040107070\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 928.08ms (926.65ms CPU time)\n\nRan 3 test suites in 929.21ms (974.08ms CPU time): 40 tests passed, 0 failed, 0 skipped (40 total tests)\n","passed":true},{"durationMs":31,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Workers.acceptOwnership()\",\"Workers.approve(address,uint256)\",\"Workers.setFeeRecipient(address)\",\"Workers.setTradeVenue(address,bool)\",\"Workers.transfer(address,uint256)\",\"Workers.transferFrom(address,address,uint256)\",\"Workers.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":10,\"DEPENDENCIES.sha256\":38,\"README.md\":69,\"foundry.toml\":21,\"remappings.txt\":2,\"src/Workers.sol\":128,\"test/Workers.t.sol\":375,\"test/WorkersFixtures.sol\":73,\"test/WorkersInvariant.t.sol\":109,\"test/WorkersLaunch.t.sol\":171},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":437,"exitCode":0,"name":"slither","output":"slither: no results at low impact or above","passed":true},{"durationMs":210,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/Workers.sol:15: Centralization Risk (4 places)\n[low] large-numeric-literal at src/Workers.sol:16: Large Numeric Literal (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f05fc461121492e28c45f23d36ad7f7121d89f33343d25187b524496f4b5b909","verifiedTreeHash":"af9271aa12ea1ff628d938ba9e11bfbe0c5fc9aa","verifierVersion":"0.1.0+ad90ce4c"}]}