{"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":"2e48a602-37f9-4e80-9976-a039e8e2cbfd","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"d9e9fb0ea22dbd94b4f1eb36eb6a8a8bf09fa10679d7b2626353481a0c48afa6","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":"ae16e03833d73c34cacf044fc8087116ed7558ef0eac5240eb79f180a41644f7","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":null,"dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":"13bdbfa814d4476434b52d6250a6acf474a9769c0e91a709f3fd4868b0597ffd","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":"bf26b4f5dbfcd1504f97b17623dd66790d69c6a98d46fb3e94dda369c4b79643","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":"842c8a22963189bb7d09745e8d00a1b9c4c395197733983921fe2a5cf1777211","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":"a5c53977d1fdcf74377b6ddf4d17df60cd28571ee3831110feb46838d05da48a","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: DCA (DEMOCRATIZI).\nToken name: DCA\nToken symbol: DEMOCRATIZI\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\nWhat it does: 5% tax, 4.5% goes to 0xc51fe7cD475a668C05fB9Cff741fBb7b6f7726Db.\n\nno image, no utility.","parentJobId":null,"planHash":"14a150865a6bde054ff07f1c89e30564cab7248d096f6f64da0ba3dc46e1f918","previousHash":"97b3a67e2f6e6d4e124f2006f8d9178e7bcda636ecfc5ca89790c171339b74f7","projectId":"2e48a602-37f9-4e80-9976-a039e8e2cbfd","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1457-dca"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51374","feedbackHash":"641da1fd59c4220ea0a4331eb58f6123111be76cdd7e35cd55d49e448946db23","nodeKey":"audit_economics","submissionHash":"d9e9fb0ea22dbd94b4f1eb36eb6a8a8bf09fa10679d7b2626353481a0c48afa6","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52587","feedbackHash":"230089fe3bb385db6a332853e5970b4ec0520617e31ce006afec6d6d34c69083","nodeKey":"audit_flow","submissionHash":"ae16e03833d73c34cacf044fc8087116ed7558ef0eac5240eb79f180a41644f7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51504","feedbackHash":"39a7e44c00cc36e2890532e64d7527dad3051728d993245711dcb14a953aa892","nodeKey":"audit_math","submissionHash":"13bdbfa814d4476434b52d6250a6acf474a9769c0e91a709f3fd4868b0597ffd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52391","feedbackHash":"ed07cb62755241c275a67a76814ae99e4a705062690091b3dd71eba05a49f1d3","nodeKey":"audit_permissions","submissionHash":"bf26b4f5dbfcd1504f97b17623dd66790d69c6a98d46fb3e94dda369c4b79643","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52347","feedbackHash":"27eae83d67880683f30da721f24444d8484694bfb6080f9eb5ac76025e584023","nodeKey":"write_foundry_tests","submissionHash":"a5c53977d1fdcf74377b6ddf4d17df60cd28571ee3831110feb46838d05da48a","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"0159a53a4e38faf55e77b815ea07d874c0978a408e45bc5ec488c6f86146337d","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6156e0902980b6f9","findings":[],"hash":"027fb591719f0c7cb51caa90a2a57052e5a4e29d36911f2718e7ed47f1c2c058","nodeId":"4deee84a-10cd-4561-9606-140439787229","outcome":"failed","summary":"needs input: DCAToken requires owner_ as its first constructor argument. The brief specifies the treasury and remainder recipient but does not designate the token owner, who can manage tax exemptions and redirect the 0.5% tax slice. The repository mentions $owner, but token constructor placeholders are limited to $factory, $poolManager and $launchNumber; an explicit owner address is required. — Which Ethereum address should be passed as DCAToken's owner_ constructor argument to control tax exemptions and the optional 0.5% tax recipient?","treeHash":null,"usage":{"cachedInputTokens":221056,"inputTokens":38899,"model":"gpt-6-astra","outputTokens":1608,"runtime":"codex","turns":3,"wallClockMs":63324}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0238a59bba722237","findings":[{"citation":"resolved","description":"Boundary: the exemption is keyed on the raw PoolManager address for both directions, not on the launch pool or on a swap. The v4 PoolManager is permissionless and exposes currency-level functions that need no pool at all: unlock() -> sync(currency) -> ERC-20 transfer into the manager -> settle() -> take(currency, to, amount), or mint(to, id, amount) of ERC-6909 claim tokens that the recipient later burns and takes. Each leg of such a relay is a token transfer whose `to` or `from` is POOL_MANAGER, so isTaxed() returns false twice and nothing reaches the treasury or the burn. Any EOA can do this with a ~60-line callback contract for roughly 150k gas, which is cheaper than the 5% tax for any transfer worth more than a few dollars. The README states the tax applies to wallet-to-wallet transfers, CEX deposits and withdrawals, and non-v4 venues; in practice it is voluntary for anyone who routes through the manager, and the treasury's 4.5% revenue (the token's only function) can be avoided on every large transfer. The PoolManager exemption itself is required by the launch floor (a token that taxes the manager cannot be seeded or sold), so there is no in-token guard that distinguishes a swap settlement from a relay: both arrive as plain transfers to the manager. The decision is the requester's: either accept and disclose that the tax is bypassable (correct the README's claim), or collect the tax inside a v4 hook on the launch pool instead of in the ERC-20, which is a design change beyond this token. Unrelated owner powers are not involved; the trigger is unprivileged.","line":151,"path":"src/DCAToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DCAToken} from \"src/DCAToken.sol\";\n\ninterface IERC20Min {\n    function transfer(address to, uint256 amount) external returns (bool);\n    function balanceOf(address account) external view returns (uint256);\n}\n\ninterface IUnlockCb {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @notice The subset of the Uniswap v4 PoolManager that any address may use without a pool:\n///         unlock -> sync -> (ERC-20 transfer in) -> settle -> take. The real manager exposes the\n///         same functions, permissionlessly, guarded only by the unlock lock and a zero net delta.\ncontract RelayPoolManager {\n    address locker;\n    address synced;\n    uint256 reserves;\n    int256 delta;\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 = IUnlockCb(msg.sender).unlockCallback(data);\n        require(delta == 0, \"nonzero delta\");\n        locker = address(0);\n    }\n\n    function sync(address currency) external {\n        synced = currency;\n        reserves = IERC20Min(currency).balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        require(msg.sender == locker, \"not locker\");\n        paid = IERC20Min(synced).balanceOf(address(this)) - reserves;\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        require(IERC20Min(currency).transfer(to, amount), \"take failed\");\n    }\n}\n\n/// @notice Anyone's relay: moves `amount` of `token` from itself to `to` through the manager.\ncontract Relay is IUnlockCb {\n    RelayPoolManager immutable manager;\n\n    constructor(RelayPoolManager m) {\n        manager = m;\n    }\n\n    function send(address token, address to, uint256 amount) external {\n        manager.unlock(abi.encode(token, to, amount));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (address token, address to, uint256 amount) = abi.decode(data, (address, address, uint256));\n        manager.sync(token);\n        IERC20Min(token).transfer(address(manager), amount); // to == POOL_MANAGER: untaxed\n        manager.settle();\n        manager.take(token, to, amount); // from == POOL_MANAGER: untaxed\n        return \"\";\n    }\n}\n\ncontract PoolManagerRelayTest is Test {\n    address constant OWNER = address(0x0A11CE);\n    address constant POOL_MANAGER = address(0x900C);\n    address constant FACTORY = address(0xFAC7);\n    address constant BOB = address(0xB0B);\n\n    DCAToken token;\n    Relay relay;\n\n    function setUp() public {\n        vm.etch(POOL_MANAGER, address(new RelayPoolManager()).code);\n        vm.prank(FACTORY);\n        token = new DCAToken(OWNER, FACTORY, POOL_MANAGER, 7);\n        relay = new Relay(RelayPoolManager(POOL_MANAGER));\n        // Alice's wallet is the relay contract; it holds 1,000 DCA received tax-free from the factory.\n        vm.prank(FACTORY);\n        token.transfer(address(relay), 1_000 ether);\n    }\n\n    /// @dev An ordinary holder moves 1,000 DCA to Bob through the PoolManager and pays no tax.\n    ///      The documented behaviour is that a wallet-to-wallet transfer pays 5%: Bob 950, treasury 45,\n    ///      5 burned. Fails on the current code because Bob receives 1,000 and the treasury 0.\n    function test_relayThroughPoolManagerIsTaxed() public {\n        uint256 supplyBefore = token.totalSupply();\n        relay.send(address(token), BOB, 1_000 ether);\n\n        assertEq(token.balanceOf(address(relay)), 0, \"sender paid the full amount\");\n        assertEq(token.balanceOf(POOL_MANAGER), 0, \"the manager keeps nothing\");\n        assertEq(token.balanceOf(token.TREASURY()), 45 ether, \"treasury should have received 4.5%\");\n        assertEq(token.balanceOf(BOB), 950 ether, \"recipient should have received 95%\");\n        assertEq(supplyBefore - token.totalSupply(), 5 ether, \"0.5% should have been burned\");\n    }\n}","reproduction":"State: DCAToken deployed with POOL_MANAGER = the v4 PoolManager; Alice holds 1,000 DCA in a contract implementing IUnlockCallback (or an EOA driving one). Sequence: (1) relay.send(token, Bob, 1000e18) calls PoolManager.unlock(data); (2) in unlockCallback: manager.sync(DCA); token.transfer(POOL_MANAGER, 1000e18) -> to == POOL_MANAGER so isTaxed() is false, 1000e18 moves whole; manager.settle() credits +1000e18 delta to the relay; manager.take(DCA, Bob, 1000e18) -> the manager transfers 1000e18 to Bob, from == POOL_MANAGER so again untaxed; unlock ends with zero delta. Expected (per README 'How the tax works'): Bob 950e18, treasury 45e18, 5e18 burned. Actual: Bob 1000e18, treasury 0, supply unchanged, PoolManager balance 0. test/scratch/PoolManagerRelay.t.sol reproduces this with a minimal manager etched at POOL_MANAGER exposing exactly the v4 functions used; run `forge test --match-path test/scratch/PoolManagerRelay.t.sol` and it fails on the current code with 'treasury should have received 4.5%: 0 != 45000000000000000000'.","severity":"medium","snippet":"        if (from == POOL_MANAGER || to == POOL_MANAGER) return false;","title":"Any holder can move any amount tax-free by relaying it through the Uniswap v4 PoolManager (sync/settle/take or ERC-6909 mint/burn)"},{"citation":"resolved","description":"The token's constructorArgs may only be static words or $factory, $poolManager and $launchNumber; $token, $requester and $contract:Name exist for application contracts only, and no $owner placeholder exists anywhere in the manifest schema. The NatSpec here and README.md line 88 ('Manifest value: $owner') both direct the manifest author to write an unresolvable placeholder for the first constructor argument. A launch.json that follows the docs is refused at admission (or, if a resolver treated the unknown placeholder as zero, the constructor reverts with OwnableInvalidOwner(0) and the launch fails). The owner must be written as a literal lowercase address (the requester's wallet, e.g. 0xc51fe7cd475a668c05fb9cff741fbb7b6f7726db if that is who should administer exemptions). Fix: change the NatSpec and README to say the owner is a static address word chosen by the requester, and name which one.","line":91,"path":"src/DCAToken.sol","reproduction":"State: launch.json written as documented: \"constructorArgs\": [\"$owner\", \"$factory\", \"$poolManager\", \"$launchNumber\"]. Expected: manifest accepted and the token constructed with the requester's wallet as owner. Actual: \"$owner\" is not a schema placeholder for token.constructorArgs, so the manifest is rejected; with a literal address in slot 0 instead (e.g. \"0xc51fe7cd475a668c05fb9cff741fbb7b6f7726db\") the same bytecode constructs correctly, as test/DCAToken.t.sol shows with a plain address OWNER.","severity":"low","snippet":"    ///               manifest fills this with `$owner`; it is never the factory or the launch wallet.","title":"Constructor docs tell the manifest step to pass `$owner`, a placeholder the token manifest schema does not have"},{"citation":"resolved","description":"Both slices round down, so the fee truncates to zero below the per-slice threshold: amount*450/10000 is 0 for amount <= 22 and amount*50/10000 is 0 for amount <= 199. Rounding favours the payer, the opposite of the usual fee rule. Quantified: moving one whole DCA (1e18 wei) tax-free needs at least 4.55e16 transfers of 22 wei, at ~50k gas each, so the leak is dust with no compounding and cannot be used at scale; the existing test test_tinyTransferRoundsTaxDownInFavourOfRecipient documents it as intended. Recorded for completeness of the math review; no change is required. If the author prefers protocol-favouring rounding, round each slice up (ceil) and keep net = amount - treasury - secondary, which still conserves value since treasury + secondary <= amount for amount >= 1.","line":167,"path":"src/DCAToken.sol","reproduction":"Alice holds 100 wei. Alice.transfer(Bob, 22): previewTax(22) = (22, 0, 0); Bob +22, treasury +0, supply unchanged (tax 0% instead of 5%). Alice.transfer(Bob, 23): (22, 1, 0), treasury +1, nothing burned. Alice.transfer(Bob, 199): (190, 8, 0), the 0.5% slice is 0. Alice.transfer(Bob, 200): (189, 9, 1). Verified with previewTax in test/scratch/Probe.t.sol.","severity":"info","snippet":"        treasuryAmount = (amount * TREASURY_BPS) / BPS;\n        secondaryAmount = (amount * SECONDARY_BPS) / BPS;","title":"Zero-rounding: transfers of at most 22 wei pay no tax and at most 199 wei skip the burn/secondary slice (not economically exploitable)"}],"hash":"13bdbfa814d4476434b52d6250a6acf474a9769c0e91a709f3fd4868b0597ffd","nodeId":"c10ef9e8-7125-4f07-b1a7-ae69c4a72b7f","outcome":"completed","summary":"The review is complete. The findings file is at the repository root with 3 findings and 12 coverage rows, and no project file was changed.\n\n**What I found**\n\n- **Medium, PoolManager relay bypass.** The tax exemption at `src/DCAToken.sol:151` is keyed on the raw PoolManager address in both directions. The v4 manager is permissionless and offers unlock, sync, settle and take with no pool involved. Any holder can transfer into the manager and take out to any recipient, and both legs are untaxed. Alice moving 1,000 DCA to Bob this way leaves Bob with 1,000 and the treasury with 0, where the README promises 950, 45 and 5 burned. The proof test fails on the current code with exactly that treasury mismatch. The exemption itself is mandated by the launch floor, so the fix is a requester decision: disclose that the tax is bypassable, or collect it in a v4 hook instead of the ERC-20.\n- **Low, `$owner` placeholder.** The constructor NatSpec and README tell the manifest step to pass `$owner`, which the token manifest schema does not define. The owner must be a literal lowercase address word.\n- **Info, dust rounding.** Transfers of 22 wei or less pay no tax, and 199 wei or less skip the burn slice. Moving one whole token this way takes more than 4.5e16 transfers, so it is not exploitable at scale.\n\n**Math and boundary coverage**\n\nThe arithmetic otherwise holds. Fuzzing confirmed net plus treasury plus secondary equals the amount, total tax never exceeds 5%, and the gross balance check keeps the amount under 1e27 so no intermediate can overflow. Self-transfers, a secondary recipient equal to the sender, zero-value transfers, the burn sentinel branch, and the three factory-lookup failure modes all behave as documented. One lead stayed unreported: a factory returning a word with dirty upper bits would brick taxed transfers, but the factory is the trusted IdentityMD contract.\n\n**Checks run**\n\n| Check | Result |\n| --- | --- |\n| Project suite | 39 passed |\n| Scratch boundary probe | 6 passed |\n| Relay proof on current code | fails as intended |","treeHash":null,"usage":{"cachedInputTokens":966146,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":25078,"runtime":"claude","turns":24,"wallClockMs":365167}},{"artifacts":[],"attempt":1,"bundleHash":"c299af69694b644eacb429e376f4c213552f4c233c268efb92d5be3c79b29b55","device":"6ef494db85781eec","findings":[],"hash":"842c8a22963189bb7d09745e8d00a1b9c4c395197733983921fe2a5cf1777211","nodeId":"4316756c-c266-4ced-9839-ddafffff5cd3","outcome":"completed","summary":"The work is committed and verified. Nothing further is needed from the environment.\n\n**Delivered: `DCAToken` (`src/DCAToken.sol`)**, a fixed-supply ERC-20 named `DCA` with symbol `DEMOCRATIZI`, 1,000,000,000 tokens at 18 decimals minted once to the deploying factory. Every ordinary transfer pays 5%: 4.5% goes to the treasury constant `0xc51fe7cD475a668C05fB9Cff741fBb7b6f7726Db`, and the recipient receives 95%.\n\n**Checks run locally, all passing:**\n- `forge build` with solc 0.8.26, `bytecode_hash = \"none\"`, runtime 4.7 KB\n- `forge test`: 39 tests across two suites, including a 256-run fuzz on value conservation, rerun with an empty environment\n- `forge fmt --check`\n- No git submodules. forge-std 1.11.0 and the OpenZeppelin 5.4.0 ERC20/Ownable2Step subset are vendored as plain files under `lib/`.\n\n**Two judgment calls you should know about, both documented in the README:**\n- The brief names where 4.5% goes but not the remaining 0.5%. It is burned by default. The owner can redirect it after launch with `setSecondaryTaxRecipient`, so no redeploy is needed if a second wallet was intended.\n- Transfers into or out of the Uniswap v4 PoolManager are untaxed, as the launch policy requires. Buys and sells on the launch pool therefore pay no tax. The tax applies to wallet-to-wallet transfers, exchange deposits, and any other venue. Factory and MerkleDistributor transfers are also exempt so the swarm share, seed and claims arrive whole.\n\n**Owner powers** are limited to managing tax exemptions and the 0.5% recipient. There is no mint, pause, freeze, blacklist, seize or rate change, and the launch exemptions survive renouncing ownership. Ownership transfer is two-step. The constructor takes four static arguments, `$owner`, `$factory`, `$poolManager` and `$launchNumber`, and calls no other contract.\n\nThe README covers assumptions, the manifest constructor arguments, the optional after-launch settings, and operational responsibilities. Static analysers were not available here and did not run, and an independent adversarial review is still due before release.","treeHash":"61b99b6da2e1007bb91ee03cb6ddca3d7574917e","usage":{"cachedInputTokens":573677,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":34844,"runtime":"claude","turns":25,"wallClockMs":425900}},{"artifacts":[],"attempt":1,"bundleHash":"4777af17cc1d27d8503428dab3cb52cd5163c0d19f8fdaa6d17487d06a49139c","device":"24bd93bfc66ecb96","findings":[{"description":"launchDistributor documents returning the zero address when the factory cannot be read. It handles a failed staticcall and short return data, but abi.decode(data, (address)) reverts for a 32-byte word with nonzero upper 96 bits. Every non-exempt transfer and transferFrom performs this lookup, so this malformed response makes ordinary holder transfers revert instead of continuing with the normal tax. This is a defensive integration defect: reproduction requires invalid data from the configured immutable factory; no way for an ordinary holder to make a correct factory emit that data is demonstrated. A normal correctly encoded distributor response is unaffected. Validate address encoding before decoding and use the existing zero-address fallback on invalid encoding.","line":145,"path":"src/DCAToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DCAToken} from \"src/DCAToken.sol\";\n\ncontract MalformedDistributorProof is Test {\n    function test_unreadableDistributorDoesNotBlockOrdinaryTransfers() public {\n        address factory = makeAddr(\"factory\");\n        address alice = makeAddr(\"alice\");\n        address bob = makeAddr(\"bob\");\n        vm.prank(factory);\n        DCAToken token = new DCAToken(address(this), factory, makeAddr(\"pool\"), 1);\n        vm.prank(factory);\n        token.transfer(alice, 1_000 ether);\n\n        // A full ABI word with dirty upper 96 bits is not a valid encoded address.\n        vm.mockCall(factory, abi.encodeWithSignature(\"distributorOf(uint64)\", uint64(1)), abi.encode(type(uint256).max));\n        vm.prank(alice);\n        assertTrue(token.transfer(bob, 1_000 ether));\n        assertEq(token.launchDistributor(), address(0), \"an unreadable lookup should fall back to no distributor\");\n        assertEq(token.balanceOf(bob), 950 ether);\n        assertEq(token.balanceOf(0xc51fe7cD475a668C05fB9Cff741fBb7b6f7726Db), 45 ether);\n        assertEq(token.totalSupply(), 1_000_000_000 ether - 5 ether);\n    }\n}","reproduction":"Deploy with launch number 1, minting to the test factory, and transfer 1000 ether token units from the factory to Alice. Mock distributorOf(uint64(1)) on that same factory to return abi.encode(type(uint256).max), a successful call with exactly 32 bytes. Have Alice transfer 1000 ether to Bob. Expected: the unreadable distributor is treated as absent, the transfer succeeds, Bob receives 950 ether, the fixed treasury receives 45 ether, and 5 ether is burned. Actual: the transfer reverts with empty revert data at the address ABI decode, before updating any balances. Verified with forge test --match-path test/scratch/MalformedDistributor.t.sol -vvvv (exit 1, EvmError: Revert).","severity":"low","title":"Malformed distributor response bypasses the intended lookup fallback and blocks taxable transfers"},{"description":"The public pure previewTax(uint256) helper multiplies amount by 450 before dividing by 10000, so inputs above type(uint256).max / 450 panic instead of returning their tax split. All three mathematical results fit in uint256. The function has no documented input bound. This is limited to the view API: the constructor supply is 1e27 and transfers check the gross balance before previewing, so this finding does not demonstrate a failure for an attainable transfer or a loss of funds. Use full-precision mulDiv or quotient/remainder arithmetic for the public preview.","line":167,"path":"src/DCAToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DCAToken} from \"src/DCAToken.sol\";\n\ncontract PreviewTaxOverflowProof is Test {\n    function test_previewSupportsAmountsWhoseResultFitsUint256() public {\n        DCAToken token = new DCAToken(address(this), makeAddr(\"factory\"), makeAddr(\"pool\"), 1);\n        uint256 amount = type(uint256).max;\n        // Division before multiplication avoids overflowing the intermediate product.\n        uint256 expectedTreasury = (amount / 200) * 9 + ((amount % 200) * 9) / 200;\n        uint256 expectedSecondary = amount / 200;\n        (uint256 net, uint256 treasury, uint256 secondary) = token.previewTax(amount);\n        assertEq(treasury, expectedTreasury);\n        assertEq(secondary, expectedSecondary);\n        assertEq(net, amount - expectedTreasury - expectedSecondary);\n    }\n}","reproduction":"Call previewTax(115792089237316195423570985008687907853269984665640564039457584007913129639935) on a freshly deployed token. Expected net=110002484775450385652392435758253512460606485432358535837484704807517473157939, treasury=5210644015679228794060694325390955853397149309953825381775591280356090833797, secondary=578960446186580977117854925043439539266349923328202820197287920039565648199; their sum is the input. Actual: Panic(0x11), arithmetic overflow at amount * TREASURY_BPS. Verified with forge test --match-path test/scratch/PreviewTaxOverflow.t.sol -vvvv (exit 1).","severity":"low","title":"previewTax overflows for large uint256 inputs even though all resulting amounts fit"}],"hash":"a5c53977d1fdcf74377b6ddf4d17df60cd28571ee3831110feb46838d05da48a","nodeId":"2a4882ba-0cf2-4736-a4f5-fe2beca64c49","outcome":"completed","summary":"Added failure-path tests, fuzz properties, and stateful invariants.\n\n- `forge build` and `forge test` pass: 73 tests.\n- Invariants passed 32,768 randomized calls.\n- Reported two low-severity defects with reproduced failing proofs in `.imd-findings.json`: large-input preview overflow and malformed distributor-response handling.","treeHash":"f38846100af6d1683857d67a51485f8e70da387b","usage":{"cachedInputTokens":1051776,"inputTokens":115921,"model":"gpt-6-astra","outputTokens":24470,"runtime":"codex","turns":6,"wallClockMs":698328}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fb1af3f11ddf6470","findings":[{"citation":"resolved","description":"isTaxed() exempts every transfer whose from or to is POOL_MANAGER, whatever its purpose. Uniswap v4's PoolManager lets any contract that unlocks it settle a currency and take it out to any address, and it needs no pool to do so: unlock -> sync(DCA) -> DCA.transfer(PM, X) -> settle() -> take(DCA, recipient, X). Both legs hit this branch: to == POOL_MANAGER, then from == POOL_MANAGER. So a payment from one wallet to another reaches the recipient whole, and the treasury (0xc51f...26Db) gets nothing and nothing is burned. The README says the tax 'applies to wallet-to-wallet transfers, CEX deposits and withdrawals, and any other venue'. It does not for anyone willing to deploy a ~30-line relay or use a router that settles and takes, and it costs only gas, roughly 100k. The brief's 4.5% to the treasury can therefore be avoided on any transfer, not only on launch-pool trades. A holder can also keep DCA as ERC-6909 claims on the PoolManager and move those claims tax-free. Root cause: the launch policy requires the PoolManager exemption so that the seed and swaps move exactly, and the token grants that exemption without distinguishing a swap from a settle-and-take relay. A full fix needs a scope decision from the requester, because policy requires PM flows to be exact. Options: accept and document that the tax is voluntary for contract users, so the README must stop promising a wallet-to-wallet tax; or narrow the exemption, for example by taxing PM->recipient transfers that are not the output of a swap, which the token cannot tell apart without a hook. Either way, the README guarantee is currently false.","line":151,"path":"src/DCAToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DCAToken} from \"src/DCAToken.sol\";\n\n/// @dev The part of Uniswap v4's PoolManager flash accounting a relay uses, with v4's semantics:\n/// sync snapshots the reserve, settle credits the caller with what arrived since, take pays a\n/// credited currency out to any address. No pool is needed for any of it.\ncontract FlashAccountingManager {\n    address public synced;\n    uint256 public reserves;\n    mapping(address => int256) public delta;\n\n    function sync(address currency) external {\n        synced = currency;\n        reserves = DCAToken(currency).balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        paid = DCAToken(synced).balanceOf(address(this)) - reserves;\n        delta[msg.sender] += int256(paid);\n        synced = address(0);\n    }\n\n    function take(address currency, address to, uint256 amount) external {\n        delta[msg.sender] -= int256(amount);\n        require(delta[msg.sender] >= 0, \"CurrencyNotSettled\");\n        DCAToken(currency).transfer(to, amount);\n    }\n}\n\n/// @dev What any holder can deploy: hand it tokens, it relays them through the manager.\ncontract Relay {\n    function send(DCAToken token, FlashAccountingManager manager, address to, uint256 amount) external {\n        manager.sync(address(token));\n        token.transferFrom(msg.sender, address(manager), amount);\n        manager.settle();\n        manager.take(address(token), to, amount);\n    }\n}\n\ncontract PoolManagerRelayTest is Test {\n    address constant TREASURY = 0xc51fe7cD475a668C05fB9Cff741fBb7b6f7726Db;\n    address constant OWNER = address(0x0A11CE);\n    address constant ALICE = address(0xA11CE);\n    address constant BOB = address(0xB0B);\n\n    DCAToken token;\n    FlashAccountingManager manager;\n    Relay relay;\n\n    function setUp() public {\n        manager = new FlashAccountingManager();\n        relay = new Relay();\n        token = new DCAToken(OWNER, address(this), address(manager), 1);\n        token.transfer(ALICE, 1_000_000 ether); // factory send, untaxed\n    }\n\n    /// @dev README: \"The tax applies to wallet-to-wallet transfers\". Alice pays Bob 1,000,000 DCA.\n    /// A direct transfer pays 45,000 to the treasury; the same payment through the PoolManager\n    /// pays it nothing.\n    function test_walletToWalletPaymentThroughThePoolManagerSkipsTheTax() public {\n        uint256 amount = 1_000_000 ether;\n        uint256 treasuryBefore = token.balanceOf(TREASURY);\n\n        vm.startPrank(ALICE);\n        token.approve(address(relay), amount);\n        relay.send(token, manager, BOB, amount);\n        vm.stopPrank();\n\n        assertEq(token.balanceOf(ALICE), 0, \"Alice paid the whole amount\");\n        // On the code as it is, Bob receives 1,000,000 whole and the treasury receives 0.\n        assertEq(\n            token.balanceOf(TREASURY) - treasuryBefore,\n            (amount * 450) / 10_000,\n            \"a wallet-to-wallet payment reached Bob without paying the treasury its 4.5%\"\n        );\n    }\n}","reproduction":"State: token deployed with poolManager_ = PM (v4 PoolManager), ALICE holds 1,000,000 DCA, the treasury holds 0. ALICE approves a relay contract R for 1,000,000 DCA. R calls PM.unlock; in unlockCallback it calls PM.sync(DCA), DCA.transferFrom(ALICE, PM, 1e24), PM.settle(), and PM.take(DCA, BOB, 1e24). Expected, as for any wallet-to-wallet transfer: BOB receives 950,000, the treasury 45,000, and 5,000 is burned. Actual: BOB receives 1,000,000, the treasury 0, and totalSupply is unchanged. The proof uses a minimal stand-in with v4's sync/settle/take semantics, because v4-core is not vendored in lib/. It fails with '0 != 45000000000000000000000'.","severity":"medium","snippet":"        if (from == POOL_MANAGER || to == POOL_MANAGER) return false;","title":"Anyone can send DCA wallet-to-wallet without paying the 5% tax by passing it through the PoolManager's flash accounting"},{"citation":"resolved","description":"The token's first constructor parameter is owner_, and the NatSpec (line 91) and the README deployment table say the manifest fills it with `$owner`. A custom-token launch resolves only static words and $factory, $poolManager and $launchNumber in token.constructorArgs. `$owner` is not one of them, and $requester is allowed only for application contracts. A manifest written as documented, [\"$owner\",\"$factory\",\"$poolManager\",\"$launchNumber\"], cannot be resolved. If the placeholder resolves to the zero address, OZ Ownable reverts in the constructor, so the factory's CREATE2 fails and the launch cannot deploy. The brief names no owner at all; it names only the 4.5% wallet. So no static value follows from the job either, and whoever the manifest step picks becomes the party that can redirect 0.5% of every taxed transfer (setSecondaryTaxRecipient) and exempt any address (setTaxExempt). Fix without changing behaviour: document the owner as a static address that the requester confirms (for example the brief's wallet 0xc51fe7cd475a668c05fb9cff741fbb7b6f7726db, which is also economics.remainderTo), and correct the NatSpec and README. Alternatively, drop the parameter and hard-code that owner.","line":97,"path":"src/DCAToken.sol","reproduction":"`new DCAToken(address(0), address(0xFAC7), address(0x900C), 1)` reverts with OwnableInvalidOwner(address(0)), confirmed with a scratch test. A launch.json token.constructorArgs of [\"$owner\",\"$factory\",\"$poolManager\",\"$launchNumber\"], as README 'Deployment parameters' prescribes, uses a placeholder the deployer does not resolve for a custom token. Expected: a constructor argument list the manifest can express. Actual: either an unresolvable manifest or a reverting constructor, and the launch does not deploy.","severity":"medium","snippet":"    constructor(address owner_, address factory_, address poolManager_, uint64 launchNumber_)\n        ERC20(\"DCA\", \"DEMOCRATIZI\")\n        Ownable(owner_)","title":"Constructor needs an owner the launch manifest cannot supply: the documented `$owner` is not a token placeholder and zero reverts"}],"hash":"ae16e03833d73c34cacf044fc8087116ed7558ef0eac5240eb79f180a41644f7","nodeId":"4f42bf97-2f2a-4d0e-976d-89ffb5ed5af7","outcome":"completed","summary":"I found two medium-severity defects and wrote them, with coverage rows for all 8 entry points, to `.imd-findings.json`. No repository files were changed; my scratch tests are deleted. The project's own 39 tests pass.\n\n**1. Anyone can send tokens to another wallet tax-free by routing them through the Uniswap v4 PoolManager** (`src/DCAToken.sol:151`, medium)\n- **Cause:** the token exempts every transfer into or out of the PoolManager, and the launch rules require that exemption. But the PoolManager lets any contract pay tokens in and take them out to any address, with no pool or swap involved.\n- **Effect:** a holder can pay another wallet the full amount, and the treasury at `0xc51f…26Db` gets nothing. It costs only gas, roughly 100k. The README's promise that wallet-to-wallet transfers pay 5% is false.\n- **Reproduction:** Alice sends 1,000,000 tokens to Bob through a small relay contract. Bob receives all 1,000,000 and the treasury receives 0, where 45,000 was expected.\n- **Proof:** the attached Foundry test fails on the current code with `0 != 45000000000000000000000`. It runs against a minimal stand-in that copies the PoolManager's pay-in and take-out behaviour, because the real PoolManager code is not in `lib/`.\n- **Fix:** this needs a decision from the requester, since the exemption has to stay. Either document that the tax can be avoided this way, or narrow the exemption, which would probably need a pool hook.\n\n**2. The owner argument cannot be filled in as documented** (`src/DCAToken.sol:97`, medium)\n- **Cause:** the code comment and README say the launch manifest fills the constructor's `owner_` with `$owner`. A custom token's constructor accepts only fixed values and `$factory`, `$poolManager` and `$launchNumber`, so `$owner` cannot be filled in.\n- **Effect:** if it ends up as the zero address, the constructor reverts with `OwnableInvalidOwner` (I confirmed this), so the token never deploys. The brief also names no owner, yet whoever is chosen can redirect the 0.5% share and exempt any address from the tax.\n- **Fix:** have the requester confirm a fixed owner address and correct the comment and README.\n\n**Coverage:**\n- `transfer` and `transferFrom` are rows for finding 1.\n- The other six entry points hold:\n  - The three ownership functions are standard two-step ownership.\n  - `approve` is the standard ERC-20 approval.\n  - `setTaxExempt` cannot touch the factory or PoolManager exemptions.\n  - `setSecondaryTaxRecipient` holds too. The static analyser's missing-zero-check warning is wrong here: zero deliberately means the 0.5% is burned.\n- I also added a row for the constructor (finding 2) and checked three invariants, all of which hold: the supply can never grow, launch transfers arrive exact, and each taxed transfer's parts add up to the amount sent.\n- I left out the PoolManager seed and swap path. That test needs v4-core, which isn't in `lib/`, so it isn't proven here.","treeHash":null,"usage":{"cachedInputTokens":607988,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":20025,"runtime":"claude","turns":13,"wallClockMs":225681}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"743b05d8a235188d","findings":[{"citation":"resolved","description":"Area: Access Control (confused deputy) x Trust Gap seam 1 (access x economics). isTaxed() exempts every transfer whose `from` or `to` is the Uniswap v4 PoolManager, regardless of who initiated it. The v4 PoolManager is a permissionless settlement layer: inside `unlock`, anyone may `sync(currency)`, transfer tokens in, `settle()` to be credited, and then `take(currency, to, amount)` to send that credit to ANY address. Both legs of such a pass-through are exempt here: leg 1 (holder -> PoolManager) matches `to == POOL_MANAGER`, leg 2 (PoolManager -> recipient) matches `from == POOL_MANAGER`. So the PoolManager acts as a tax-free deputy for every holder, with no swap, no pool and no owner involvement. Uniswap's deployed V4Router/UniversalRouter already exposes this as the SETTLE + TAKE(recipient) action pair, so no custom contract is even needed. The same holds for ERC-6909 claim tokens minted in the PoolManager (settle with claims=true, then transfer the claim, then burn+take), and for any other v4 pool of this token that a third party creates (DCA/USDC etc.), since the exemption is on the PoolManager address, not the launch pool. README.md:39-43 promises that 'The tax applies to wallet-to-wallet transfers, CEX deposits and withdrawals, and any other venue' and README.md:78-79 tells the requester to 'expect tax revenue only from transfers outside that pool'; neither holds: every transfer outside the pool can be made untaxed at the cost of one router call, so the 4.5% treasury stream and the 0.5% burn/secondary slice are collectable only from holders who do not know this. Precondition: none beyond holding tokens; actor: any unprivileged holder. Impact: the documented tax guarantee is broken for the treasury (0xc51fe7cD475a668C05fB9Cff741fBb7b6f7726Db); no holder principal is at risk, so medium. This cannot be fixed by narrowing the exemption without breaking the launch floor (a swap's settle/take legs are indistinguishable from a pass-through at the token level, and the protected test requires buys and sells to move whole). The minimal honest fix is a design/scope decision for the requester: either (a) accept and document that the tax is avoidable through the PoolManager, any v4 router and ERC-6909 claims, correcting README.md:39-43 and 78-79, or (b) change the economics (e.g. tax only `to`-side transfers into non-PoolManager addresses from PoolManager is still bypassable; a genuinely enforced tax would need a hook-gated pool that this launch's single-sided seed does not provide). Report this to the requester before launch so the tax revenue expectation is realistic.","line":151,"path":"src/DCAToken.sol","reproduction":"State: DCAToken deployed by the factory with POOL_MANAGER = the v4 PoolManager; ALICE holds 1,000e18 DCA received from the factory. Sequence (any unprivileged holder):\n1. ALICE deploys (or uses an existing) router R that implements IUnlockCallback.\n2. ALICE: token.approve(R, 1000e18).\n3. ALICE: R.send(token, BOB, 1000e18) -> R calls PoolManager.unlock(data).\n4. In R.unlockCallback: PoolManager.sync(token); token.transferFrom(ALICE, PoolManager, 1000e18) -> _update(ALICE, PoolManager, 1000e18): isTaxed(R, ALICE, PoolManager) returns false at line 151 (to == POOL_MANAGER), so the full 1000e18 moves; PoolManager.settle() credits R +1000e18; PoolManager.take(token, BOB, 1000e18) -> _update(PoolManager, BOB, 1000e18): isTaxed(PoolManager, PoolManager, BOB) returns false at line 151 (from == POOL_MANAGER), so the full 1000e18 moves; R's delta is zero so unlock completes.\nExpected (per README.md:21-28 and 39-43, a wallet-to-wallet transfer): BOB 950e18, TREASURY +45e18, totalSupply -5e18, TaxCollected emitted.\nActual: BOB 1000e18, TREASURY +0, totalSupply unchanged, no TaxCollected. Verified locally with test/scratch/PoolManagerPassThrough.t.sol, which models exactly the v4 sync/settle/take semantics in a PoolManager stand-in (v4-core is not vendored in this tree): `forge test --match-path test/scratch/PoolManagerPassThrough.t.sol` -> test_anyHolderMovesTokensWalletToWalletUntaxedThroughThePoolManager passes, asserting BOB == 1000e18, treasury == 0, supply unchanged. The token-level fact needed is independent of the mock: token.isTaxed(0xBEEF, ALICE, POOL_MANAGER) == false and token.isTaxed(0xBEEF, POOL_MANAGER, BOB) == false for any caller (test_isTaxedExemptsBothLegsForAnyCaller).","severity":"medium","snippet":"        if (from == POOL_MANAGER || to == POOL_MANAGER) return false;","title":"Any holder bypasses the 5% tax on wallet-to-wallet transfers by routing through the PoolManager (confused deputy on the POOL_MANAGER exemption)"},{"citation":"resolved","description":"Area: Access Control (who holds the role after deployment). The constructor's first argument `owner_` receives the Ownable2Step owner, which holds setTaxExempt and setSecondaryTaxRecipient for the token's whole life. README.md:88 and the NatSpec at src/DCAToken.sol:90-91 (\"The launch manifest fills this with `$owner`\") say the manifest resolves it with a `$owner` placeholder. The custom-token launch rules supplied with this task allow the token's constructorArgs to be only static words or `$factory`, `$poolManager`, `$launchNumber` (`$token`, `$requester`, `$contract:Name` exist only for application contracts). There is no `$owner`. So the manifest step must write a static lowercase address, and the brief names no owner wallet at all (it names only the 4.5% treasury, which is also economics.remainderTo). Consequence: the address that controls the token's admin powers is not fixed by the requester's brief or by the code and is picked at manifest time; a manifest that copies `$owner` literally is refused, and one that picks any address other than the requester's quietly hands a stranger the exemption and 0.5%-slice powers (no unprivileged amplifier, so low). Minimal fix: state the intended owner explicitly (the brief's treasury 0xc51fe7cd475a668c05fb9cff741fbb7b6f7726db is the only requester address given) in README.md:88 and the constructor NatSpec, and have the manifest step write that static address; alternatively hardcode the owner as a constant like TREASURY so the role holder is fixed in bytecode and the constructor takes only the three launch arguments.","line":88,"path":"README.md","reproduction":"Input: a launch.json token entry following README.md:84-91 verbatim: \"constructorArgs\": [\"$owner\", \"$factory\", \"$poolManager\", \"$launchNumber\"]. Expected (per README): `$owner` resolves to the requester's wallet. Actual: `$owner` is not a placeholder the token's constructorArgs accept (only static words, $factory, $poolManager, $launchNumber), so the manifest is refused; the only way through is a static address, and with the brief naming no owner, the manifest author decides who holds owner(). Concretely, constructorArgs [\"0x000000000000000000000000000000000000beef\", \"$factory\", \"$poolManager\", \"$launchNumber\"] deploys successfully (constructor only rejects address(0) at src/DCAToken.sol:99-101) and gives 0xBEEF, not the requester, the power to call setTaxExempt and setSecondaryTaxRecipient.","severity":"low","snippet":"| `owner_` | `address` | `$owner` (the requester's wallet) |","title":"Owner is documented as a `$owner` manifest placeholder that the custom-token manifest does not provide; the owner wallet is therefore undefined by the brief and chosen by whoever writes launch.json"},{"citation":"resolved","description":"Not a defect; recorded as required for privileged powers. The brief asks for a 5% tax with 4.5% to the treasury and says nothing about an owner. The build adds an Ownable2Step owner with two powers, both guarded correctly by onlyOwner (verified: stranger and factory revert with OwnableUnauthorizedAccount; no other writer of isTaxExempt or secondaryTaxRecipient exists; launch exemptions at lines 150-154 are read before/independently of the mapping so the owner cannot un-exempt the factory, PoolManager or distributor, and setTaxExempt refuses FACTORY/POOL_MANAGER at line 122). Powers to be aware of: (1) setTaxExempt(x, true) exempts x in BOTH directions (line 152), so an exempted address is a tax-free sink and source; the owner can exempt its own wallets or affiliates and move tokens untaxed, and can act as a tax-free relay for others. (2) setTaxExempt(TREASURY, false) is allowed (line 119 comment) and makes the treasury pay 5% when it moves what it collected. (3) setSecondaryTaxRecipient(any) redirects the 0.5% slice from burn to any address including the owner (line 131-134); no pending value exists so there is no retroactive sweep. (4) Owner cannot mint, pause, freeze, seize, change TAX_BPS/TREASURY_BPS/TREASURY (constants), confirmed by reading every external function. (5) renounceOwnership() freezes the current exemption set and secondary recipient forever. Actor: the owner only; preconditions: holding the owner key named in finding 2. Recommended: keep as documented in README.md:51-68, and decide the owner wallet explicitly (finding 2).","line":120,"path":"src/DCAToken.sol","reproduction":"Owner = O. (a) O: setTaxExempt(O, true); factory had sent O 1000e18; O: transfer(BOB, 1000e18) -> BOB 1000e18, treasury +0 (exempt sender). (b) O: setTaxExempt(TREASURY, false); treasury: transfer(BOB, 1000e18) -> BOB 950e18, treasury keeps 45e18, 5e18 burned. (c) O: setSecondaryTaxRecipient(O); ALICE: transfer(BOB, 1000e18) -> O +5e18 instead of a burn. All three succeed only from O; from 0xBEEF each reverts with OwnableUnauthorizedAccount(0xBEEF).","severity":"info","snippet":"    function setTaxExempt(address account, bool exempt) external onlyOwner {","title":"Trust assumptions: owner powers that the brief did not ask for (tax exemptions in both directions, revocable treasury exemption, redirectable 0.5% slice)"}],"hash":"bf26b4f5dbfcd1504f97b17623dd66790d69c6a98d46fb3e94dda369c4b79643","nodeId":"b1d5ddb2-42c5-45d4-a137-b0a48f527b42","outcome":"completed","summary":"Review is complete. The findings file is valid JSON, all three snippets match the tree at the cited lines, and all 8 entry points plus 5 invariants have coverage rows.\n\n**Outcome**\n\nOne substantive defect, one low process gap, and one trust-assumption record are in `.imd-findings.json`.\n\n- **Medium, tax bypass through the PoolManager.** The exemption on line 151 of `src/DCAToken.sol` applies to any transfer whose sender or recipient is the Uniswap v4 PoolManager, no matter who initiated it. Since v4's sync, settle and take are permissionless inside an unlock, any holder can relay tokens wallet-to-wallet through the PoolManager and both legs are exempt. The deployed Uniswap router already exposes this as a SETTLE plus TAKE pair. The README's promise that off-pool transfers pay 5% does not hold. I confirmed the mechanics with a scratch test modelling v4 settlement: 1,000 tokens arrive whole where 950 was expected. This cannot be narrowed without breaking the launch floor, so I flagged it as a design decision for the requester.\n- **Low, undefined owner wallet.** The README and NatSpec say the manifest fills the owner with a `$owner` placeholder. The launch rules only allow static words or the three launch placeholders for the token, and the brief names no owner. Whoever writes the manifest picks the admin.\n- **Info, owner trust assumptions.** Exemptions apply in both directions, the treasury's exemption is revocable, and the 0.5% slice is redirectable. All guards verified correct.\n\n**What held:** ownership two-step and renounce, approve, supply never grows, launch flows move whole, no privileged balance moves, and the vendored OpenZeppelin files are byte-identical to upstream 5.4.0. Slither's zero-check lead was rejected since zero is a valid burn state.\n\n**Not reached:** nothing in my area. The scratch test sits in `test/scratch/` and is not kept.","treeHash":null,"usage":{"cachedInputTokens":661685,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":23811,"runtime":"claude","turns":24,"wallClockMs":341558}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"13603a08ada71730","findings":[{"citation":"resolved","description":"isTaxed() exempts every transfer whose sender OR recipient is the Uniswap v4 PoolManager. The PoolManager is a general-purpose contract: inside unlock(), any locker may sync(), transfer tokens in, settle() for a credit, then take(currency, to, amount) to any address, with no pool or swap involved. Each leg is exempt (to == POOL_MANAGER on the way in, from == POOL_MANAGER on the way out), so an ordinary transfer from A to B through a 20-line relay contract pays 0 tax instead of 5%. The same holds via ERC-6909 claims (settle + mint claim, transfer the claim off-chain of the token, burn + take). Treasury revenue from wallet-to-wallet transfers, CEX deposits and OTC moves is therefore only collected from users who choose to pay it; the brief's '5% tax, 4.5% to the treasury' is not an enforced guarantee. The exemption on the sell direction (to == POOL_MANAGER) is required for swaps to settle (a taxed sell shorts the PoolManager's delta and reverts under the protected harness's TraderProbe). The exemption on the take direction (from == POOL_MANAGER) is not needed for solvency: take() debits the full amount from the locker and the token moves exactly that amount out of the manager's balance (95% to the recipient, 4.5% to the treasury, 0.5% burned), so the protected floor (buy arrives > 0, sell of what was bought settles exactly) still passes. Taxing that direction closes the bypass and turns the tax into a buy-tax on the launch pool; it must be weighed against the launch note that says to exempt msg.sender == PoolManager. If the requester keeps both exemptions, the README must state that the tax can be bypassed by anyone through the PoolManager, not merely that pool trades are untaxed.","line":151,"path":"src/DCAToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {DCAToken} from \"src/DCAToken.sol\";\n\n/// @dev Deploys the token so the supply is minted to this contract, like the factory, and answers\n///      `distributorOf` with nothing recorded.\ncontract FactoryStub {\n    mapping(uint64 => address) public distributorOf;\n\n    function deploy(address owner, address poolManager, uint64 launchNumber) external returns (DCAToken) {\n        return new DCAToken(owner, address(this), poolManager, launchNumber);\n    }\n\n    function move(IERC20 token, address to, uint256 amount) external returns (bool) {\n        return token.transfer(to, amount);\n    }\n}\n\ninterface IUnlockCallback {\n    function unlockCallback(bytes calldata data) external returns (bytes memory);\n}\n\n/// @dev The Uniswap v4 PoolManager flows the token exempts, reduced to the three primitives any\n///      locker may call with no pool involved at all: `sync` records the reserve, `settle` credits\n///      the locker with whatever arrived since, `take` sends tokens out to any address. The real\n///      manager does exactly this (Currency transfers in `take`, balance deltas in `settle`) and\n///      requires the locker's delta to be zero when the lock closes.\ncontract PoolManagerStub {\n    IERC20 public immutable token;\n    address private locker;\n    uint256 private reserve;\n    int256 private delta;\n\n    constructor(IERC20 token_) {\n        token = token_;\n    }\n\n    function unlock(bytes calldata data) external returns (bytes memory result) {\n        require(locker == address(0), \"AlreadyUnlocked\");\n        locker = msg.sender;\n        result = IUnlockCallback(msg.sender).unlockCallback(data);\n        require(delta == 0, \"CurrencyNotSettled\");\n        locker = address(0);\n    }\n\n    function sync() external {\n        reserve = token.balanceOf(address(this));\n    }\n\n    function settle() external returns (uint256 paid) {\n        require(msg.sender == locker, \"ManagerLocked\");\n        paid = token.balanceOf(address(this)) - reserve;\n        reserve = token.balanceOf(address(this));\n        delta += int256(paid);\n    }\n\n    function take(address to, uint256 amount) external {\n        require(msg.sender == locker, \"ManagerLocked\");\n        delta -= int256(amount);\n        require(token.transfer(to, amount), \"transfer failed\");\n    }\n}\n\n/// @dev What any holder can deploy: moves `amount` from the caller to `to` through the manager,\n///      entering as a sell would and leaving as a buy would, without touching a pool.\ncontract PassThroughRelay is IUnlockCallback {\n    PoolManagerStub public immutable manager;\n    IERC20 public immutable token;\n\n    constructor(PoolManagerStub manager_, IERC20 token_) {\n        manager = manager_;\n        token = token_;\n    }\n\n    function send(address to, uint256 amount) external {\n        manager.unlock(abi.encode(msg.sender, to, amount));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not the manager\");\n        (address from, address to, uint256 amount) = abi.decode(data, (address, address, uint256));\n        manager.sync();\n        require(token.transferFrom(from, address(manager), amount), \"pull failed\");\n        manager.settle();\n        manager.take(to, amount);\n        return \"\";\n    }\n}\n\ncontract PoolManagerPassThroughTest is Test {\n    address constant OWNER = address(0x0A11CE);\n    address constant ALICE = address(0xA11CE);\n    address constant BOB = address(0xB0B);\n    uint64 constant LAUNCH_NUMBER = 7;\n\n    FactoryStub factory;\n    DCAToken token;\n    PoolManagerStub manager;\n    PassThroughRelay relay;\n\n    function setUp() public {\n        factory = new FactoryStub();\n        // The manager's address must be known before the token is built; precompute it.\n        address predictedManager = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        token = factory.deploy(OWNER, predictedManager, LAUNCH_NUMBER);\n        manager = new PoolManagerStub(IERC20(address(token)));\n        require(address(manager) == predictedManager, \"manager prediction mismatch\");\n        relay = new PassThroughRelay(manager, IERC20(address(token)));\n        assertTrue(factory.move(IERC20(address(token)), ALICE, 1_000 ether));\n    }\n\n    /// @dev A wallet-to-wallet transfer is supposed to pay 5%: 4.5% to the treasury, 0.5% burned.\n    ///      Routed through the manager it pays nothing: Bob receives the whole 1000, the treasury\n    ///      receives 0 and nothing is burned. Fails on the current code; passes once a transfer\n    ///      that leaves the manager for an ordinary address is taxed like any other transfer.\n    function test_passThroughTheManagerIsTaxedLikeAnOrdinaryTransfer() public {\n        uint256 supplyBefore = token.totalSupply();\n        assertEq(token.balanceOf(token.TREASURY()), 0);\n\n        vm.prank(ALICE);\n        token.approve(address(relay), 1_000 ether);\n        vm.prank(ALICE);\n        relay.send(BOB, 1_000 ether);\n\n        assertEq(token.balanceOf(ALICE), 0, \"Alice paid the gross amount\");\n        assertEq(token.balanceOf(address(manager)), 0, \"the manager kept nothing\");\n        assertEq(token.balanceOf(token.TREASURY()), 45 ether, \"the treasury did not receive 4.5%\");\n        assertEq(token.balanceOf(BOB), 950 ether, \"Bob received more than 95%\");\n        assertEq(token.totalSupply(), supplyBefore - 5 ether, \"the 0.5% slice was not burned\");\n    }\n}","reproduction":"State: factory funds ALICE with 1000e18 (exempt). ALICE approves a relay contract; relay calls PoolManager.unlock -> in unlockCallback: sync(token); token.transferFrom(ALICE, POOL_MANAGER, 1000e18) [isTaxed: to == POOL_MANAGER -> false, untaxed]; settle() [locker credited 1000e18]; take(token, BOB, 1000e18) [PoolManager calls token.transfer(BOB, 1000e18): from == POOL_MANAGER -> untaxed]. Expected for a wallet-to-wallet transfer: BOB 950e18, TREASURY +45e18, totalSupply -5e18. Actual: BOB 1000e18, TREASURY 0, supply unchanged. Run: forge test --match-path test/scratch/PoolManagerPassThrough.t.sol (fails: 'the treasury did not receive 4.5%: 0 != 45000000000000000000'). With `if (to == POOL_MANAGER) return false;` in place of line 151 (sells stay exempt, takes are taxed) the same test passes.","severity":"medium","snippet":"        if (from == POOL_MANAGER || to == POOL_MANAGER) return false;","title":"Any holder can move tokens wallet-to-wallet tax-free by routing through the PoolManager (sync/settle/take), so the 5% tax is voluntary"},{"citation":"resolved","description":"The token's constructorArgs may only be static words or $factory, $poolManager, $launchNumber. There is no $owner placeholder (and $requester is defined only for application contracts). owner_ is the first constructor argument and Ownable reverts on the zero address, so the manifest must carry a concrete lowercase address for it. Following the documented instruction produces a manifest that is refused, or, if the step substitutes blindly, an owner the requester did not choose. The README's deployment table (README.md line 88) repeats the same instruction.","line":91,"path":"src/DCAToken.sol","reproduction":"Write launch.json with token.constructorArgs = [\"$owner\", \"$factory\", \"$poolManager\", \"$launchNumber\"] as src/DCAToken.sol:91 and README.md:88 instruct. Expected: a manifest the schema accepts. Actual: \"$owner\" is not a resolvable word for the token's constructorArgs; the owner must be a static address, e.g. the requester's wallet 0xc51fe7cd475a668c05fb9cff741fbb7b6f7726db. Fix: document the owner as a static address (name which one) in both places.","severity":"low","snippet":"    ///               manifest fills this with `$owner`; it is never the factory or the launch wallet.","title":"Constructor NatSpec and README tell the manifest step to use `$owner`, a placeholder the launch manifest does not define"},{"citation":"resolved","description":"The brief says '5% tax, 4.5% goes to 0xc51fe7...'. The code sends 4.5% to the treasury and burns the other 0.5% until the owner sets secondaryTaxRecipient, after which that slice goes to any address the owner names (the owner's own wallet included). Three readings are plausible: burn 0.5%, send the whole 5% to the treasury, or a second wallet. The build chose burn-by-default plus an owner switch and documents it as an assumption. This is an open economic question for the requester, not a code defect; it is recorded so the judge can have it confirmed before launch. If the whole 5% should reach the treasury, the owner can set secondaryTaxRecipient = TREASURY after launch with no code change, or the constant split can be collapsed before launch.","line":196,"path":"src/DCAToken.sol","reproduction":"Factory funds ALICE with 1000e18. ALICE transfer(BOB, 1000e18) with secondaryTaxRecipient unset. Actual: BOB 950e18, TREASURY 45e18, totalSupply drops by 5e18 (see test_ordinaryTransferPaysFivePercentTax). Then OWNER setSecondaryTaxRecipient(OWNER) and ALICE transfers again: the 5e18 now goes to OWNER. Expected: whatever the requester intended for the 0.5%, which the brief does not state.","severity":"info","snippet":"            if (secondary == address(0)) super._update(from, address(0), secondaryAmount);","title":"0.5% of each taxed transfer is burned by default and owner-redirectable; the brief gives it no destination and the requester has not confirmed either reading"}],"hash":"d9e9fb0ea22dbd94b4f1eb36eb6a8a8bf09fa10679d7b2626353481a0c48afa6","nodeId":"e1a249f2-d353-4ec4-819f-1a5ba8f0e8b9","outcome":"completed","summary":"The review is complete. No tracked file was changed; the findings are in `.imd-findings.json` at the repository root, and the one proof test lives under `test/scratch/`.\n\n**What I found**\n\n- **Medium: the 5% tax is voluntary.** Both PoolManager directions are exempt at `src/DCAToken.sol:151`. Any holder can deploy a small relay that, inside `unlock`, syncs, pulls tokens into the PoolManager, settles, and takes them out to any address. No pool or swap is involved, and each leg is untaxed. Alice sending Bob 1000 tokens this way leaves Bob with 1000, the treasury with 0 and nothing burned, instead of 950 / 45 / 5. The same path exists through ERC-6909 claims. The proof test fails on the current code and passes when only the sell direction stays exempt and transfers leaving the PoolManager are taxed. That variant keeps the protected floor passing, since takes move exactly the debited amount out of the manager, but it turns the tax into a buy tax and sits against the launch note about exempting the PoolManager as sender. That is a requester decision; at minimum the README should say the tax is bypassable, not merely that pool trades are untaxed.\n- **Low: `$owner` is not a manifest word.** The constructor NatSpec at line 91 and the README deployment table instruct the manifest step to use `$owner`. The token's constructor arguments allow only static words or the three launch placeholders, so the owner must be written as a static address.\n- **Info: the 0.5% slice has no destination in the brief.** The code burns it by default and lets the owner redirect it anywhere after launch. The requester should confirm burn, whole 5% to treasury, or a second wallet.\n\n**What held**\n\nAll eight entry points have coverage rows, plus six invariant rows. Supply conservation, the 5% cap with floor rounding, launch flows arriving whole, view/write agreement, the factory staticcall fallback, and the two-step ownership paths all behave as intended. Sub-23-wei transfers escape the tax, but gas makes that worthless, so it is noted in coverage rather than reported.\n\n**Not reached**\n\nThe real ProjectFactory is not in the tree, so I could not confirm it has no function that lets an outsider make it call the token (the `caller == FACTORY` exemption rests on that). That remains a trust assumption, not a finding.","treeHash":null,"usage":{"cachedInputTokens":583164,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":27408,"runtime":"claude","turns":22,"wallClockMs":400261}}],"verification":[{"checks":[{"durationMs":1175,"exitCode":0,"name":"build","output":"Compiling 33 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.07s\nCompiler run successful!\n","passed":true},{"durationMs":89,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/DeployDCAToken.t.sol:DeployDCATokenTest\n[PASS] test_deployUsesTheConfigVerbatim() (gas: 3152212)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 382.51µs (204.42µs CPU time)\n\nRan 38 tests for test/DCAToken.t.sol:DCATokenTest\n[PASS] testFuzz_taxSplitConservesValue(uint256) (runs: 256, μ: 224571, ~: 227805)\nLogs:\n  Bound result 7696\n\n[PASS] test_clearingSecondaryRecipientReturnsToBurning() (gas: 251411)\n[PASS] test_constructorMintsWholeSupplyToDeployerOnce() (gas: 39941)\n[PASS] test_constructorRecordsLaunchAddressesAndOwner() (gas: 48722)\n[PASS] test_constructorRejectsZeroLaunchAddresses() (gas: 4059)\n[PASS] test_distributorClaimsAreUntaxed() (gas: 198091)\n[PASS] test_distributorIsLookedUpAtTransferTimeNotConstruction() (gas: 217805)\n[PASS] test_distributorOfAnotherLaunchIsNotExempt() (gas: 208958)\n[PASS] test_exemptRecipientReceivesWhole() (gas: 177255)\n[PASS] test_factoryTransfersAreUntaxed() (gas: 189228)\n[PASS] test_launchExemptionsSurviveOwnerAndRenounce() (gas: 65523)\n[PASS] test_metadata() (gas: 29201)\n[PASS] test_noAdminCallMovesOrFreezesAHolder() (gas: 1165666)\n[PASS] test_noMintFunctionExists() (gas: 145876)\n[PASS] test_nonOwnerCannotSetExemption() (gas: 36114)\n[PASS] test_nonOwnerCannotSetSecondaryRecipient() (gas: 60329)\n[PASS] test_ordinaryTransferPaysFivePercentTax() (gas: 192054)\n[PASS] test_ownerCanExemptAndRevoke() (gas: 329894)\n[PASS] test_ownerCanRenounce() (gas: 69350)\n[PASS] test_ownerMayRevokeTreasuryExemption() (gas: 189082)\n[PASS] test_ownerSetsSecondaryRecipientAndSliceIsRoutedThere() (gas: 269403)\n[PASS] test_ownershipTransferIsTwoStep() (gas: 130719)\n[PASS] test_poolManagerSeedBuyAndSellMoveWhole() (gas: 195213)\n[PASS] test_previewTaxMatchesTransfer() (gas: 6416)\n[PASS] test_runtimeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 1851089)\n[PASS] test_setExemptRejectsZeroAndLaunchAddresses() (gas: 86177)\n[PASS] test_strangerCannotTakeOwnership() (gas: 60270)\n[PASS] test_taxAppliesOnEveryHop() (gas: 260074)\n[PASS] test_taxConstants() (gas: 21606)\n[PASS] test_tinyTransferRoundsTaxDownInFavourOfRecipient() (gas: 151925)\n[PASS] test_transferFromIsTaxedAndSpendsFullAllowance() (gas: 237815)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 91934)\n[PASS] test_transferRevertsWhenBalanceTooLow() (gas: 102376)\n[PASS] test_transferToZeroAddressReverts() (gas: 89284)\n[PASS] test_transfersKeepWorkingWhenFactoryHasNoCode() (gas: 161348)\n[PASS] test_transfersKeepWorkingWhenFactoryLookupReverts() (gas: 292175)\n[PASS] test_treasuryMovesCollectedTaxWithoutPayingAgain() (gas: 238677)\n[PASS] test_zeroValueTransferSucceeds() (gas: 53885)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 6.74ms (18.88ms CPU time)\n\nRan 2 test suites in 7.28ms (7.12ms CPU time): 39 tests passed, 0 failed, 0 skipped (39 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"DCAToken.acceptOwnership()\",\"DCAToken.approve(address,uint256)\",\"DCAToken.renounceOwnership()\",\"DCAToken.setSecondaryTaxRecipient(address)\",\"DCAToken.setTaxExempt(address,bool)\",\"DCAToken.transfer(address,uint256)\",\"DCAToken.transferFrom(address,address,uint256)\",\"DCAToken.transferOwnership(address)\"],\"files\":{\".gitignore\":5,\"README.md\":143,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployDCAToken.s.sol\":35,\"src/DCAToken.sol\":201,\"src/interfaces/ILaunchFactory.sol\":9,\"test/DCAToken.t.sol\":538,\"test/DeployDCAToken.t.sol\":24,\"test/mocks/MockLaunchFactory.sol\":37},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":510,"exitCode":0,"name":"slither","output":"[low/medium] missing-zero-check at src/DCAToken.sol:131: DCAToken.setSecondaryTaxRecipient(address).recipient (src/DCAToken.sol#131) lacks a zero-check on :","passed":true},{"durationMs":243,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/DCAToken.sol:27: Centralization Risk (3 places)\n[low] large-numeric-literal at src/DCAToken.sol:33: Large Numeric Literal (2 places)\n[low] state-no-address-check at src/DCAToken.sol:133: Address State Variable Set Without Checks","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"842c8a22963189bb7d09745e8d00a1b9c4c395197733983921fe2a5cf1777211","verifiedTreeHash":"61b99b6da2e1007bb91ee03cb6ddca3d7574917e","verifierVersion":"0.1.0+7ff5db2c"},{"checks":[{"durationMs":3903,"exitCode":0,"name":"build","output":"Compiling 38 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.73s\nCompiler run successful!\n","passed":true},{"durationMs":53967,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/DeployDCAToken.t.sol:DeployDCATokenTest\n[PASS] test_deployUsesTheConfigVerbatim() (gas: 3152212)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 544.73µs (289.86µs CPU time)\n\nRan 9 tests for test/DCAToken.Properties.t.sol:DCATokenPropertiesTest\n[PASS] testFuzz_delegatedTransfersChargeGrossAndPreserveInfiniteAllowance(uint256,uint256,bool,bool) (runs: 1000, μ: 316274, ~: 305483)\nLogs:\n  Bound result 85481783917186802991272735\n  Bound result 17\n\n[PASS] testFuzz_ordinaryRoundTripNeverCreatesValue(uint256,bool) (runs: 1000, μ: 332838, ~: 336850)\nLogs:\n  Bound result 15474950445055098603671219\n\n[PASS] testFuzz_poolRoundTripPreservesSupplyAndHolderBalance(uint256,bool) (runs: 1000, μ: 244557, ~: 273411)\nLogs:\n  Bound result 270353365683\n\n[PASS] testFuzz_previewConservesAmountAndBoundsRounding(uint256) (runs: 1000, μ: 11551, ~: 11277)\nLogs:\n  Bound result 995542402451706882559657270\n\n[PASS] testFuzz_previewHasExactTwoHundredWeiPeriod(uint256,uint256) (runs: 1000, μ: 20432, ~: 20597)\nLogs:\n  Bound result 154\n  Bound result 14084\n\n[PASS] testFuzz_secondaryAliasingConservesBalances(uint256,uint8) (runs: 1000, μ: 344497, ~: 342202)\nLogs:\n  Bound result 5000000000000000000000000\n  Bound result 0\n\n[PASS] testFuzz_shortFactoryResponsesFallBackWithoutBlockingTransfers(uint8) (runs: 1000, μ: 197100, ~: 196852)\nLogs:\n  Bound result 17\n\n[PASS] test_entireInitialSupplyCanBeTransferredAndTaxed() (gas: 223732)\n[PASS] test_roundingBoundariesOnActualTransfers() (gas: 1262090)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 116.13ms (769.58ms CPU time)\n\nRan 38 tests for test/DCAToken.t.sol:DCATokenTest\n[PASS] testFuzz_taxSplitConservesValue(uint256) (runs: 256, μ: 222946, ~: 227805)\nLogs:\n  Bound result 539160388955001827152790612\n\n[PASS] test_clearingSecondaryRecipientReturnsToBurning() (gas: 251411)\n[PASS] test_constructorMintsWholeSupplyToDeployerOnce() (gas: 39941)\n[PASS] test_constructorRecordsLaunchAddressesAndOwner() (gas: 48722)\n[PASS] test_constructorRejectsZeroLaunchAddresses() (gas: 4059)\n[PASS] test_distributorClaimsAreUntaxed() (gas: 198091)\n[PASS] test_distributorIsLookedUpAtTransferTimeNotConstruction() (gas: 217805)\n[PASS] test_distributorOfAnotherLaunchIsNotExempt() (gas: 208958)\n[PASS] test_exemptRecipientReceivesWhole() (gas: 177255)\n[PASS] test_factoryTransfersAreUntaxed() (gas: 189228)\n[PASS] test_launchExemptionsSurviveOwnerAndRenounce() (gas: 65523)\n[PASS] test_metadata() (gas: 29201)\n[PASS] test_noAdminCallMovesOrFreezesAHolder() (gas: 1165666)\n[PASS] test_noMintFunctionExists() (gas: 145876)\n[PASS] test_nonOwnerCannotSetExemption() (gas: 36114)\n[PASS] test_nonOwnerCannotSetSecondaryRecipient() (gas: 60329)\n[PASS] test_ordinaryTransferPaysFivePercentTax() (gas: 192054)\n[PASS] test_ownerCanExemptAndRevoke() (gas: 329894)\n[PASS] test_ownerCanRenounce() (gas: 69350)\n[PASS] test_ownerMayRevokeTreasuryExemption() (gas: 189082)\n[PASS] test_ownerSetsSecondaryRecipientAndSliceIsRoutedThere() (gas: 269403)\n[PASS] test_ownershipTransferIsTwoStep() (gas: 130719)\n[PASS] test_poolManagerSeedBuyAndSellMoveWhole() (gas: 195213)\n[PASS] test_previewTaxMatchesTransfer() (gas: 6416)\n[PASS] test_runtimeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 1851089)\n[PASS] test_setExemptRejectsZeroAndLaunchAddresses() (gas: 86177)\n[PASS] test_strangerCannotTakeOwnership() (gas: 60270)\n[PASS] test_taxAppliesOnEveryHop() (gas: 260074)\n[PASS] test_taxConstants() (gas: 21606)\n[PASS] test_tinyTransferRoundsTaxDownInFavourOfRecipient() (gas: 151925)\n[PASS] test_transferFromIsTaxedAndSpendsFullAllowance() (gas: 237815)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 91934)\n[PASS] test_transferRevertsWhenBalanceTooLow() (gas: 102376)\n[PASS] test_transferToZeroAddressReverts() (gas: 89284)\n[PASS] test_transfersKeepWorkingWhenFactoryHasNoCode() (gas: 161348)\n[PASS] test_transfersKeepWorkingWhenFactoryLookupReverts() (gas: 292175)\n[PASS] test_treasuryMovesCollectedTaxWithoutPayingAgain() (gas: 238677)\n[PASS] test_zeroValueTransferSucceeds() (gas: 53885)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 116.17ms (104.23ms CPU time)\n\nRan 23 tests for test/DCAToken.Adversarial.t.sol:DCATokenAdversarialTest\n[PASS] test_allowanceForNetAmountCannotAuthorizeGrossTransfer() (gas: 217275)\n[PASS] test_approvalCanBeReplacedAndRevokedWithoutMovingTokens() (gas: 317162)\n[PASS] test_cancellingNominationDoesNotRenounceOwnership() (gas: 124909)\n[PASS] test_clearingDistributorRestoresTaxAndDoesNotExemptIncomingTransfers() (gas: 349751)\n[PASS] test_constructorMintsToActualDeployerWhenFactoryIsDifferent() (gas: 36791)\n[PASS] test_factoryAsApprovedOperatorTransfersWholeAndSpendsGrossAllowance() (gas: 212584)\n[PASS] test_infiniteAllowanceSurvivesMultipleTransfers() (gas: 326780)\n[PASS] test_insufficientBalanceRollsBackAllowanceAndAllTaxLegs() (gas: 286656)\n[PASS] test_invalidReceiverRollsBackSpentAllowance() (gas: 217988)\n[PASS] test_maximumTransferFailsWithGrossBalanceErrorWithoutOverflow() (gas: 371764)\n[PASS] test_pendingOwnerHasNoPrivilegesAndReplacedNomineeCannotAccept() (gas: 348062)\n[PASS] test_renouncingClearsNomineeAndKeepsTransfersLive() (gas: 335949)\n[PASS] test_replacingDistributorRevokesOldAutomaticExemption() (gas: 357746)\n[PASS] test_rolesAndExemptionsNeverReplaceApproval() (gas: 451271)\n[PASS] test_secondaryAndTreasuryCanCoincideWithoutRecursiveTax() (gas: 269885)\n[PASS] test_selfTransferChargesOnlyFeesAndStillSpendsGrossAllowance() (gas: 226447)\n[PASS] test_selfTransferStillRequiresGrossBalance() (gas: 173796)\n[PASS] test_taxedTransferEmitsNetTreasuryBurnAndTaxEvents() (gas: 177078)\n[PASS] test_treasuryAsSenderAndSecondaryOnlyLosesTheNet() (gas: 242840)\n[PASS] test_zeroAmountDoesNotPermitZeroSenderOrReceiver() (gas: 104143)\n[PASS] test_zeroLaunchNumberIsPreservedAndQueried() (gas: 71501)\n[PASS] test_zeroSpenderIsRejectedEvenForZeroApproval() (gas: 37369)\n[PASS] test_zeroTransferFromNeedsNoApprovalAndEmitsTransfer() (gas: 132694)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 116.24ms (7.91ms CPU time)\n\nRan 2 tests for test/DCAToken.Invariant.t.sol:DCATokenInvariantTest\n[PASS]\nDCATokenInvariantTest invariants:\n[PASS] invariant_administrationMatchesAuthorizedTransitions\n[PASS] invariant_allBalancesMatchLedgerAndSumToSupply\n[PASS] invariant_allowancesMatchGrossSpendingAndFailedCallsAreAtomic\n DCATokenInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-----------------+------------------------+-------+---------+----------╮\n| Contract        | Selector               | Calls | Reverts | Discards |\n+=======================================================================+\n| DCATokenHandler | approve                | 2726  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | approveAndTransferFrom | 2734  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | ownership              | 2617  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | rejectOverdraft        | 2778  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | rejectTransferFrom     | 2717  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | rejectZeroReceiver     | 2702  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | setDistributor         | 2716  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | setExemption           | 2840  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | setSecondary           | 2733  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | transfer               | 2780  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | transferFrom           | 2762  | 0       | 0        |\n|-----------------+------------------------+-------+---------+----------|\n| DCATokenHandler | unauthorizedAdmin      | 2663  | 0       | 0        |\n╰-----------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 2578\n  Bound result 0\n  Bound result 3\n  Bound result 31\n  Bound result 904\n  Bound result 756309224837172923163665267\n  Bound result 2251\n  Bound result 19\n  Bound result 0\n  Bound result 1444\n  Bound result 78\n  Bound result 0\n  Bound result 14454\n  Bound result 4\n  Bound result 0\n  Bound result 827905\n  Bound result 76\n  Bound result 12413\n  Bound result 79\n  Bound result 0\n  Bound result 49\n  Bound result 0\n  Bound result 67051987766868904955\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 94\n  Bound result 0\n  Bound result 0\n  Bound result 42\n\n[PASS] test_handlerSequenceExercisesAllEconomicModes() (gas: 5051809)\nLogs:\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 10000000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n  Bound result 1000000000000000000003\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 53.84s (53.85s CPU time)\n\nRan 5 test suites in 53.85s (54.19s CPU time): 73 tests passed, 0 failed, 0 skipped (73 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"DCAToken.acceptOwnership()\",\"DCAToken.approve(address,uint256)\",\"DCAToken.renounceOwnership()\",\"DCAToken.setSecondaryTaxRecipient(address)\",\"DCAToken.setTaxExempt(address,bool)\",\"DCAToken.transfer(address,uint256)\",\"DCAToken.transferFrom(address,address,uint256)\",\"DCAToken.transferOwnership(address)\"],\"files\":{\".gitignore\":5,\"README.md\":143,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployDCAToken.s.sol\":35,\"src/DCAToken.sol\":201,\"src/interfaces/ILaunchFactory.sol\":9,\"test/DCAToken.Adversarial.t.sol\":331,\"test/DCAToken.Invariant.t.sol\":145,\"test/DCAToken.Properties.t.sol\":168,\"test/DCAToken.t.sol\":538,\"test/DeployDCAToken.t.sol\":24,\"test/TESTING.md\":55,\"test/handlers/DCATokenHandler.sol\":309,\"test/helpers/DCATokenFixture.sol\":52,\"test/mocks/MockLaunchFactory.sol\":37},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a5c53977d1fdcf74377b6ddf4d17df60cd28571ee3831110feb46838d05da48a","verifiedTreeHash":"f38846100af6d1683857d67a51485f8e70da387b","verifierVersion":"0.1.0+7ff5db2c"}]}