{"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":"99da3350-ad43-49bb-9340-2d1deb221c04","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"a7a48384523a64ddecbe45529c1bc90c46dc3375a6d8d9d8117dfe97f62b5eb2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d7161d0fa2b820dff954c809c96317e535e64f71e530989746eb7d0d3c874dfc","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f37f4593c199328bafc71767afa332fff231930f23998d3785bb7703c9e0213c","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"28c5c1e2fd1282a2a4cb307e4d95198f75f46a81d913a08fbab5170513abb887","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"e744bf2862b2e55ee529003d6ccb9903f970c6065636957612fe384bf094f661","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f1c65a463586ac99900a8e2fba36c2b5bd6b246443c2d3a8359a700982c1b0dd","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"801ad669b5cbc71e3c182bbb306e705aac39de8faeb738e82b6a24854dd5ab82","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"7450abc4432698d33c72fc473b84e878f1e7c195932ded50a9626db66917a445","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6c8cf11980f74125d34b6f2b05c5495824fcaadbed08be63708f2f4c4e8114a2","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"6c8cf11980f74125d34b6f2b05c5495824fcaadbed08be63708f2f4c4e8114a2","skillId":"write-foundry-tests","state":"accepted"}],"objective":"A custom token: IMD PUMP (IMDPUMP).\nToken name: IMD PUMP\nToken symbol: IMDPUMP\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\nWhat it does: For IMD PUMP, we can make both the IMD buyback-and-burn and developer IMD rewards more aggressive without taking too much money away from platform development or NFT workers.\n\nI would increase the combined allocation from 20% to 35% of eligible net platform revenue.\nRecommended aggressive but sustainable model\n\n\n\n\n\n\n\nIMD ecosystem treasury\n\n25%\n\n\nDevelopment and operations\n\n20%\n\n\nIMD NFT worker rewards\n\n20%\n\n\nIMD buyback and burn\n\n20%\n\n\nDeveloper IMD rewards\n\n15%\n\n\nThis doubles the original IMD burn allocation from 10% to 20% and increases developer rewards from 10% to 15%, while preserving 40% combined for operations and worker rewards.\nHow aggressive should we go?\nI would use 20% burn + 15% developer rewards at launch, then review sustainability after three months.\n\nFor every 100 SOL of eligible net platform revenue:\n- 20 SOL funds IMD purchases and verified burns.\n- 15 SOL funds IMD purchases transferred to the developer.\n- 65 SOL supports the treasury, operations, and NFT workers.\nThese percentages apply to net platform revenue, not total trading volume or users' funds.\n\nRaydium's platform fees and creator fees are distinct, and their actual beneficiaries and collection rules depend on configuration. \nGitHub\n \nGitHub\n\n\nI would also keep the IMD PUMP token itself free of additional transfer taxes initially. That makes trading simpler while directing legitimate platform earnings toward your IMD goals.\n\nMy preferred balance is 35% combined for IMD burns and developer accumulation. It is meaningfully more aggressive than your original 20%, but still leaves room to operate and grow the platform.\n\nWho receives fees and payouts: 0x83027AD9d0D5374335086c539dADD1db983441B0","parentJobId":null,"planHash":"387ca741e1a2058e3471958828cdd0bff4146fde93ce839f92b5b15797c4685c","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"99da3350-ad43-49bb-9340-2d1deb221c04","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-3267-imd-pump"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52166","feedbackHash":"e169f2d24574847c0f82b12c443100d722558ae7a88c5724e0a0fbddbd927b4b","nodeKey":"audit_economics","submissionHash":"a7a48384523a64ddecbe45529c1bc90c46dc3375a6d8d9d8117dfe97f62b5eb2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51083","feedbackHash":"56aa8958ad7cbad83f6f20f5a1d03699619794504ea73d7c5d60713d10ded50f","nodeKey":"audit_flow","submissionHash":"d7161d0fa2b820dff954c809c96317e535e64f71e530989746eb7d0d3c874dfc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51451","feedbackHash":"c6bfe072fdf7e5b170cc35c7ccb7a5c4703867f6c98d621cf28c4b5a1a1e2001","nodeKey":"audit_judge","submissionHash":"817957abaa34bfb83b4b9122a58943722e98c18e0875e189acc1fca79a729ac6","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51487","feedbackHash":"c07027ed2bc6506e9e46a56ae2a2ec73c8707cb8631c8345aeaffe0e56838f04","nodeKey":"audit_judge","submissionHash":"f37f4593c199328bafc71767afa332fff231930f23998d3785bb7703c9e0213c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51512","feedbackHash":"bb5c8540f523079cda45b1543d04bb4d66a7029ed005f0059418816802071347","nodeKey":"audit_math","submissionHash":"28c5c1e2fd1282a2a4cb307e4d95198f75f46a81d913a08fbab5170513abb887","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51511","feedbackHash":"3ea6efe8248a159967ff3ea500f121bb2680b3e4c48bb9e2d8020e7d3470819d","nodeKey":"audit_permissions","submissionHash":"e744bf2862b2e55ee529003d6ccb9903f970c6065636957612fe384bf094f661","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"56202","feedbackHash":"40f7f92b31d2c903181cf2c14a1f7b932a704c69f3114eeb4e47ff759cc1cbc2","nodeKey":"build_contract_project","submissionHash":"f1c65a463586ac99900a8e2fba36c2b5bd6b246443c2d3a8359a700982c1b0dd","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51538","feedbackHash":"07ba076f26bf4d5af9eae612ac18c092b6685ec29d7ab0d341daf1cb90117ff7","nodeKey":"build_contract_project","submissionHash":"649ed9d59cd6e8e3a5a1c1b600c6ec0ff18b61d17b89953ee008f8c807f39cb9","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51035","feedbackHash":"a772c9e80914868f0cdd60437395e352b4ac50c7717051b94da045199276d209","nodeKey":"manifest","submissionHash":"801ad669b5cbc71e3c182bbb306e705aac39de8faeb738e82b6a24854dd5ab82","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52422","feedbackHash":"b6108ee952ecf0c7ecc7f65abc9f05f27df9b3bd9310e2ecd10a5992f6bda441","nodeKey":"manifest","submissionHash":"0cefa8cde67b81e3a0c46ed5d4a8671a02f23667b25882530de1355b0dc76168","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52421","feedbackHash":"520054765cc895da158efbc4c3c5186047962b2b6de269a6589bf2d0723ad30f","nodeKey":"write_foundry_tests","submissionHash":"c24ff1b5b434bcefd3029ad38229b028f6d8431061260eb68622fb9a76d7d88e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52169","feedbackHash":"cf4a9e5b33b9034f01ac20f72bfc9fd0c2f40aaa7e20fb668429b6a434f69fa4","nodeKey":"write_foundry_tests","submissionHash":"7450abc4432698d33c72fc473b84e878f1e7c195932ded50a9626db66917a445","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"8ae49cd968684e30e183da7e4e26159cb2ac6c54bb4dc7161afb0bb9b1f2b8c0","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"98397c5635d6cb3b54455342eeb9abc7b2ccd1276ca60432cbc6e5c3026c5be1","device":"e35a3abed04af1ac","findings":[],"hash":"0cefa8cde67b81e3a0c46ed5d4a8671a02f23667b25882530de1355b0dc76168","nodeId":"cba641cc-19c6-48e3-8305-ac520eec662b","outcome":"completed","summary":"Created [launch.json](/home/imd6/.identitymd/work/99da3350-ad43-49bb-9340-2d1deb221c04/cba641cc-19c6-48e3-8305-ac520eec662b/launch.json) with the exact supply, constructor arguments, pool settings and economics. Notes document the pending burn configuration.\n\nManifest and compiled ABI checks passed. `forge build` succeeded; all 56 local tests passed with the supplied fuzz seed.\n\nOnly `launch.json` is changed for submission.","treeHash":"1b44206de8c9637bc4b12611c975f4bd6c7d25cf","usage":{"cachedInputTokens":219136,"cost":{"source":"estimated","usdMicros":828216},"inputTokens":36448,"model":"gpt-6-astra","outputTokens":4892,"runtime":"codex","turns":3,"wallClockMs":135044}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8c9c4986ca881107","findings":[],"hash":"28c5c1e2fd1282a2a4cb307e4d95198f75f46a81d913a08fbab5170513abb887","nodeId":"da11ece5-286f-4739-be71-8e07a86abb49","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root and validated.\n\n**Result: no defects in the math area.** The findings list is empty and the coverage record answers all 17 entry points plus four invariant and boundary rows, every one `holds`.\n\nWhat I checked against the Math Precision, Boundary and Numerical Gap guides:\n\n- **Split arithmetic.** Four shares floor-divide by 10,000 basis points and the treasury takes the remainder, so the five shares always sum exactly to the revenue. The remainder never underflows because the allocation is forced to sum to 10,000. Confirmed by fuzzing up to the full mainnet IMD supply and under random allocations.\n- **Zero-rounding.** With 4 wei of revenue the burn, developer, operations and worker shares are all zero and the treasury gets everything. The loss is under 1 wei per bucket per distribution and does not compound. The code documents this, and the auditor checklist treats dust-level non-compounding loss as not reportable.\n- **Overflow.** The largest intermediate is revenue times 2,500, which only overflows above roughly 4.6e73 wei. IMD's supply is 4.23e24, so this is unreachable.\n- **Casts and sums.** The allocation setter sums in uint256, so five maxed uint16 inputs are rejected rather than wrapped. The combined IMD-directed share stays at most 10,000, so its uint16 addition cannot revert.\n- **Balance invariant.** After every distribution the splitter's IMD balance equals the pending burn exactly, and the burn drains it to zero. A second distribute with nothing new reverts as intended.\n- **External boundary.** I read the real IMD token's bytecode and metadata from a public Ethereum RPC. It is a LayerZero OFT with plain OpenZeppelin ERC-20 transfer semantics, 18 decimals, no pause, blacklist or burn function. SafeERC20's assumptions hold for it, and any non-zero burn sink accepts transfers.\n- **Token.** Supply constant is exactly 1e27 minted once to the deployer. Self-burn reverts above balance. The vendored OpenZeppelin v5.7.0 ERC-20 is unchanged.\n\nOne trust note recorded in coverage rather than as a finding: if the owner renounces ownership before setting the burn sink, future burn shares accumulate forever since IMD cannot be recovered. That requires an owner action and has no unprivileged trigger.\n\nThe project's own 56 tests pass. My scratch tests were removed, and no repository file was changed apart from creating the findings file.","treeHash":null,"usage":{"cachedInputTokens":488044,"cost":{"source":"runtime","usdMicros":2672157},"inputTokens":226,"model":"claude-fable-5-1","outputTokens":18941,"runtime":"claude","turns":19,"wallClockMs":323187}},{"artifacts":[],"attempt":1,"bundleHash":"5b066cb1dab82ceaa6a78adba155514c6ba31912481134481fd79ea406d33cb2","device":"b57ae3a96321a0e1","findings":[],"hash":"649ed9d59cd6e8e3a5a1c1b600c6ec0ff18b61d17b89953ee008f8c807f39cb9","nodeId":"d421d380-6fe8-4923-8b29-a4b23ae2a8e2","outcome":"completed","summary":"The IMD PUMP launch is implemented, tested, documented and committed on `main` as commit `f385196`. A fresh clone builds and passes offline with an empty environment.\n\n**What was built**\n\n- `src/IMDPumpToken.sol`: the token. Name \"IMD PUMP\", symbol \"IMDPUMP\", 18 decimals, 1,000,000,000 minted once to `msg.sender` (the factory). No taxes, no owner, no mint, no pause, no blacklist, no `burnFrom`. Holders can burn their own tokens.\n- `src/IMDPumpRevenueSplitter.sol`: the application contract carrying the brief's allocation model. Revenue arrives in IMD and is split 20% burn, 15% developer, 25% treasury, 20% operations, 20% NFT workers (35% IMD-directed). Anyone can call `distribute`. Owner (the requester, a constructor argument) can re-point the four recipients, set the burn sink, and adjust the allocation for the three-month review, all through event-emitting setters. It rejects ETH, uses SafeERC20 and a reentrancy guard, and lets the owner recover stray non-IMD tokens.\n- `script/Deploy.s.sol`: a reference deployment whose `deploy(Config)` function the tests call directly.\n- `README.md`: assumptions, constructor arguments, after-launch settings, operational responsibilities and trust notes.\n- `lib/`: OpenZeppelin Contracts v5.7.0 and forge-std v1.17.0 vendored as plain files, no submodules.\n\n**One design decision worth knowing**\n\nThe IMD token on mainnet has no burn function. I checked its deployed bytecode: it has `transfer`, `transferFrom`, `owner`, but no `burn` or `burnFrom`. So a \"verified burn\" is a transfer to a burn address that only the IMD project can name. The splitter keeps that as an owner-set `burnSink`. Until it is set, the 20% burn share accrues in `pendingBurn` and every other share pays out normally. The owner should call `setBurnSink` soon after launch. This is listed in the README under \"After launch\".\n\n**Checks run**\n\n| Check | Result |\n| --- | --- |\n| `forge build` (solc 0.8.26, cancun, bytecode_hash none) | clean, no warnings |\n| `forge fmt --check` | passes |\n| `forge test` with the verifier's fuzz seed | 56 passed, 0 failed |\n| Local mirror of the floor's opcode and admin-call scans (scratch) | 3 passed |\n| Fresh clone, offline, empty environment | 56 passed |\n\nSlither was not available on this machine, so it did not run. The code avoids the six rejected detectors by construction: no `selfdestruct`, `transferFrom` only from `msg.sender`, no proxies or initializers, no shifts, no `abi.encodePacked`.\n\n**Open items for the requester or launch**\n\n- Set the burn sink after launch to the IMD project's recognised burn address.\n- The splitter's `owner_` constructor argument should be filled with the requester's owner address, and `payout_` with `0x83027AD9d0D5374335086c539dADD1db983441B0`, `revenueToken_` with the IMD address.\n- An independent adversarial review is still expected before release.","treeHash":"9faa7e48f27d0392de7582ba7bd3d15c407af80e","usage":{"cachedInputTokens":668384,"cost":{"source":"runtime","usdMicros":4507818},"inputTokens":322,"model":"claude-fable-5-1","outputTokens":50098,"runtime":"claude","turns":25,"wallClockMs":701124}},{"artifacts":[],"attempt":1,"bundleHash":"820ba6d223a400e7f43cfe0b666c73e00a4d7e61ab4b139194b3c97f428bb16d","device":"e8816d4386532a66","findings":[{"description":"Every owner setter (setAllocation, setDeveloper, setTreasury, setOperations, setNftWorkers, setBurnSink) calls _settleWaitingRevenue() before writing the new value, and that runs a full _distribute() whose SafeERC20 payouts revert if any current recipient (or the current burn sink, when pendingBurn is flushed) refuses the transfer. If a recipient becomes unable to receive the revenue token, distribute() is already blocked; the setter that would repair it is blocked too as soon as undistributedRevenue() > 0, and anyone can make that true permanently by transferring one unit of IMD to the splitter. Revenue then cannot leave through any path (IMD is not recoverable), and the owner cannot get out of it. With the IMD token as described in the README (plain OpenZeppelin ERC-20 transfers, no blacklist or pause), a transfer to a non-zero address does not revert, so the deployed configuration is not exposed today; it becomes reachable if the revenue token ever refuses a recipient (e.g. a recipient contract on a token with hooks or a blocklist). Suggested fix: let setters skip settlement (or settle with try/catch) when a payout fails, or give the owner a recipient setter that does not settle.","line":345,"path":"src/IMDPumpRevenueSplitter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IMDPumpRevenueSplitter} from \"src/IMDPumpRevenueSplitter.sol\";\n\n/// A revenue token whose transfers to one address revert (e.g. a recipient the token refuses).\ncontract RefusingToken is ERC20 {\n    address public refused;\n\n    constructor() ERC20(\"Refusing\", \"REF\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n\n    function refuse(address account) external {\n        refused = account;\n    }\n\n    function _update(address from, address to, uint256 amount) internal override {\n        require(to != refused, \"refused\");\n        super._update(from, to, amount);\n    }\n}\n\ncontract SetterDosTest is Test {\n    address internal constant OWNER = 0x83027AD9d0D5374335086c539dADD1db983441B0;\n\n    function test_ownerCanRepointARefusedRecipientWhileRevenueIsWaiting() public {\n        RefusingToken token = new RefusingToken();\n        IMDPumpRevenueSplitter s = new IMDPumpRevenueSplitter(OWNER, OWNER, address(token));\n        address brokenTreasury = makeAddr(\"broken treasury\");\n        address newTreasury = makeAddr(\"new treasury\");\n        vm.prank(OWNER);\n        s.setTreasury(brokenTreasury);\n        token.refuse(brokenTreasury);\n        // Anyone can make revenue \"waiting\" with a one-unit donation.\n        token.mint(address(s), 1);\n        // The owner's fix must go through, otherwise every distribution stays blocked forever.\n        vm.prank(OWNER);\n        s.setTreasury(newTreasury);\n        (, address treasury,,) = s.recipients();\n        assertEq(treasury, newTreasury);\n        if (s.undistributedRevenue() > 0) s.distribute();\n        assertEq(token.balanceOf(newTreasury), 1);\n        assertEq(token.balanceOf(address(s)), s.pendingBurn());\n    }\n}","reproduction":"Revenue token whose transfers to one address revert. Owner sets treasury = T, the token starts refusing T, anyone sends 1 unit to the splitter. Expected: owner's setTreasury(T2) succeeds and the waiting unit can then be distributed to T2. Actual: setTreasury(T2) reverts with the token's error ('refused') because it first tries to pay the waiting unit to T; distribute() reverts too, so every unit of revenue stays locked.","severity":"low","title":"Setters cannot re-point a recipient or sink whose payout reverts while any revenue is waiting"}],"hash":"7450abc4432698d33c72fc473b84e878f1e7c195932ded50a9626db66917a445","nodeId":"a04459ea-832f-4529-8410-b7f20151e1ab","outcome":"completed","summary":"The revised splitter had broken five of my existing tests. I updated them to the new behaviour, added tests for it, and reported one defect I couldn't test around. With the verifier's fuzz seed, `forge build` succeeds and `forge test` passes all 125 tests in my test paths. The only failure is the proof test for the defect, which is meant to fail and lives in `test/scratch/`, so it isn't submitted.\n\n**Why the tests broke.** The other contributor's last change made two behaviour changes:\n- **Setters pay out first:** every owner setter now pays out any revenue already sitting in the contract under the old settings before applying the new one.\n- **No renouncing:** `renounceOwnership` is disabled.\n\nThe changes:\n- **`test/IMDPumpRevenueProperties.t.sol`**\n  - Fixed three tests that assumed setting the burn address wouldn't trigger a payout.\n  - Replaced the renounce test with one checking the call is refused for the owner and the nominee, and that ownership and pending ownership are unchanged.\n  - New tests:\n    - All six setters pay waiting revenue to the old recipients, even when the value doesn't change. With nothing waiting, they only emit their own event.\n    - A rejected setter call pays nothing out.\n    - Someone front-running a recipient change with a 1-wei donation can't redirect it: the old recipient gets the dust.\n    - The token can't re-enter the contract during a setter's payout, including re-entering a setter.\n- **`test/invariant/IMDPumpRevenueInvariant.t.sol`:** the random-sequence handler now checks each setter's payout against the old rates, recipients and burn address. It also tries owner renounces, which must revert. All five invariants hold over 256 runs × 128 calls.\n- **`test/IMDPumpWalletGas.t.sol`:** added a check, using the supplied helper, that `setTreasury`, `setBurnSink` and `setAllocation` behave the same at a wallet's gas limit as with gas to spare when revenue is waiting.\n- **`test/TESTING.md`:** updated to match.\n\n**Reported defect (low), in `.imd-findings.json` with a runnable proof test.** Because every setter pays out waiting revenue first, the owner can't replace a recipient or burn address whose incoming transfer reverts while any revenue is waiting. `distribute()` is blocked by the same failing transfer, and anyone can keep revenue waiting by sending 1 unit of IMD. IMD then can't leave the contract at all.\n\nAccording to the README, the real IMD token uses plain transfers that don't revert for normal addresses, so today's deployment isn't exposed. That's why I rated it low. My tests don't treat this behaviour as correct.\n\nI didn't commit anything.","treeHash":"3cf7ba80e16ef538716fe8713f6eb83bc7ad021b","usage":{"cachedInputTokens":674519,"cost":{"source":"runtime","usdMicros":1034781},"inputTokens":23,"model":"claude-opus-5-5","outputTokens":16194,"runtime":"claude","turns":13,"wallClockMs":201672}},{"artifacts":[],"attempt":1,"bundleHash":"967bd11d78663434ba14a25b9eb453a33a3ede077a93c138ee52a3280c117d94","device":"8472a2af9a9cf974","findings":[],"hash":"801ad669b5cbc71e3c182bbb306e705aac39de8faeb738e82b6a24854dd5ab82","nodeId":"cba641cc-19c6-48e3-8305-ac520eec662b","outcome":"completed","summary":"The accepted manifest is correct and needs no revision. No findings were attached to this round, and every check passes.\n\n**What I verified**\n\n- The token contract takes no constructor arguments and mints the whole supply to the deployer, so an empty constructorArgs list and a totalSupply of one billion in minor units match the source.\n- The splitter constructor takes owner, payout and revenue token, in that order. The manifest passes the requester twice and the IMD address, which matches the accepted implementation.\n- The pool and economics objects are byte-for-byte the launch's values, with lowercase addresses.\n- Only the six schema keys are present, every contract name is unique and within 32 characters, and notes are well under the 4,000 character limit.\n- The build compiles cleanly and the full test suite passes with the verifier's fuzz seed, 67 tests across 3 suites.\n\n**What changed**\n\nNothing. The working tree is clean, and launch.json is left as accepted. Rewriting a verified manifest with no finding against it would only give the reviewer a different file to read for no gain.","treeHash":"5f5e646d274b41db0029a9b29bff112ff65272a9","usage":{"cachedInputTokens":100613,"cost":{"source":"runtime","usdMicros":795748},"inputTokens":98,"model":"claude-fable-5-1","outputTokens":2507,"runtime":"claude","turns":4,"wallClockMs":65890}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"79373c79d1351eba","findings":[{"citation":"resolved","description":"Merged from audit_economics, audit_flow and audit_permissions (same root cause). The contract inherits OpenZeppelin Ownable2Step and with it the one-step Ownable.renounceOwnership() (`function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); }`), and does not override it; it is a live ABI entry point. The design parks the burn share in pendingBurn until the owner calls setBurnSink (line 23-27 of the header comment; line 287 `pendingBurn += burnShare;`), and the only exits for that IMD are _executeBurn via burnPending()/_distribute(), both gated on `burnSink != address(0)` (lines 163 and 301), while recoverToken() refuses the revenue token (line 262). Every other path that could leave the system without a valid address is guarded (_checkRecipient lines 335-338, setBurnSink line 253), but renounceOwnership has no guard at all. So a single owner call while burnSink == address(0), which is the launch state, makes the sink unsettable forever: the accumulated pendingBurn can never move, every later distribute() still pays 80% out and strands a further 20%, BurnExecuted is never emitted, and the allocation and all four recipients are frozen, which also defeats the brief's planned three-month sustainability review. The brief never asks for renouncing; Ownable2Step was chosen precisely to make ownership moves non-accidental, yet this one-step irreversible call stays open. Severity medium: permanent, unrecoverable stranding of the whole burn allocation stream and permanent loss of configuration, but only through an owner action. Minimal fix preserving the design: override renounceOwnership() to revert (ownership moves only via transferOwnership/acceptOwnership), or at least `if (burnSink == address(0) || pendingBurn != 0) revert ...;` before super.renounceOwnership(). Verified with test/scratch/RenounceStrands.t.sol on this tree: both tests fail on the current code and pass once renouncing with the sink unset reverts.","line":33,"path":"src/IMDPumpRevenueSplitter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IMDPumpRevenueSplitter} from \"src/IMDPumpRevenueSplitter.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// `renounceOwnership` is inherited unguarded. If it is called while `burnSink` is still the zero\n/// address, no one can ever set the sink, `burnPending` reverts forever, `recoverToken` refuses\n/// IMD, and the 20% burn share of every past and future distribution is locked in the splitter\n/// without a `BurnExecuted` record. The fix is to refuse renouncing while the sink is unset (or to\n/// disable renouncing); this test fails until it does.\ncontract RenounceStrandsBurnTest is Test {\n    ScratchIMD imd;\n    IMDPumpRevenueSplitter splitter;\n    address owner = makeAddr(\"owner\");\n    address payout = makeAddr(\"payout\");\n\n    function setUp() public {\n        imd = new ScratchIMD();\n        splitter = new IMDPumpRevenueSplitter(owner, payout, address(imd));\n    }\n\n    function test_renounceOwnershipRefusedWhileBurnSinkUnset() public {\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        assertEq(splitter.pendingBurn(), 20e18);\n        assertEq(splitter.burnSink(), address(0));\n\n        vm.prank(owner);\n        vm.expectRevert();\n        splitter.renounceOwnership();\n\n        assertEq(splitter.owner(), owner, \"ownership was renounced with the burn share unroutable\");\n    }\n\n    function test_renouncedSplitterLocksBurnShareForever() public {\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        vm.prank(owner);\n        (bool renounced,) = address(splitter).call(abi.encodeWithSignature(\"renounceOwnership()\"));\n        if (!renounced || splitter.owner() != address(0)) return; // fixed: renounce was refused, nothing to show\n\n        // Nobody can set the sink any more.\n        vm.expectRevert();\n        splitter.setBurnSink(address(0xdead));\n        // The burn cannot run.\n        vm.expectRevert(IMDPumpRevenueSplitter.BurnSinkNotConfigured.selector);\n        splitter.burnPending();\n        // And every future distribution adds to the locked amount.\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        assertEq(splitter.pendingBurn(), 40e18);\n        assertEq(imd.balanceOf(address(splitter)), 40e18);\n        assertEq(splitter.totalBurned(), 0, \"nothing was ever verifiably burned\");\n        fail();\n    }\n}","reproduction":"State: IMDPumpRevenueSplitter(owner, payout, IMD) freshly deployed, burnSink == address(0). 1) Transfer 100e18 IMD to the splitter and call distribute() from anyone: payout receives 80e18, pendingBurn == 20e18, BurnDeferred(20e18) emitted. 2) owner calls renounceOwnership(). Expected: revert, because the burn share has no destination and no one could ever give it one. Actual: succeeds, owner() == address(0). 3) Afterwards: setBurnSink(0xdEaD) reverts OwnableUnauthorizedAccount for every caller including the former owner; burnPending() reverts BurnSinkNotConfigured; recoverToken(IMD, x, 20e18) reverts RevenueTokenNotRecoverable (and OwnableUnauthorizedAccount); transferring another 100e18 and calling distribute() raises pendingBurn to 40e18 held in the contract with totalBurned == 0 and no BurnExecuted. No code path can ever move those 40e18. forge test --match-path test/scratch/RenounceStrands.t.sol: test_renounceOwnershipRefusedWhileBurnSinkUnset FAILS ('next call did not revert as expected'), test_renouncedSplitterLocksBurnShareForever FAILS after walking the locked state.","severity":"medium","snippet":"contract IMDPumpRevenueSplitter is Ownable2Step, ReentrancyGuard {","title":"renounceOwnership() is inherited unguarded: renouncing while burnSink is unset permanently strands the 20% burn share of all past and future revenue and freezes every setting"},{"citation":"resolved","description":"Merged from audit_economics (low), audit_permissions (low) and audit_flow part (a) (info); same root cause. The README's first-class flow is a plain IMD transfer followed by distribute(). Any IMD waiting between those two steps is split by whatever is in storage when _distribute() runs: `Allocation memory a = allocation;` (line 277) and `Recipients memory r = recipients;` (line 294). setAllocation (lines 211-217) and setDeveloper/setTreasury/setOperations/setNftWorkers (lines 225, 232, 239, 246) overwrite those values without settling the waiting revenue first and without refusing to run while revenue is waiting, and distribute() (line 156) is callable by anyone. So whoever loses by a pending owner change (a developer whose share is being cut, a recipient whose key is being replaced because it is compromised) can see the owner's transaction and call distribute() ahead of it, paying itself the old share of the entire waiting balance; the reverse order pays revenue earned under the old terms to a newly re-pointed recipient. The owner cannot make the outcome deterministic from inside the contract, and 'announce before calling setAllocation' (README) does not close the window because the operator's delivery and the owner's setter are separate transactions. Impact is bounded by the revenue waiting at the moment of the change and the actor must be a current recipient (or hold its key), so low. Two minimal fixes, both preserving the permissionless distribute(): (a) have each setter settle first, `if (undistributedRevenue() > 0) _distribute();` before writing; or (b) have each setter revert while undistributedRevenue() > 0 so the owner flushes deliberately under the old terms first. The attached proof passes under either fix. Operationally, delivering only through depositAndDistribute removes the window but the contract cannot enforce that. Part (b) of audit_flow's info finding (lifetime counters count any IMD anyone sends) is dropped: that is documented behaviour, not a defect.","line":277,"path":"src/IMDPumpRevenueSplitter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IMDPumpRevenueSplitter} from \"src/IMDPumpRevenueSplitter.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// Revenue already delivered to the splitter is split under whatever allocation and recipients are\n/// in storage when the permissionless distribute() runs. The owner's setters neither settle the\n/// waiting revenue first nor refuse to run while revenue is waiting, so the split applied to it is\n/// decided by a mempool race. Either fix makes this test pass: settle first (developer gets 150e18\n/// and nothing is left waiting), or revert the setter while undistributedRevenue() > 0.\ncontract AllocationRaceTest is Test {\n    ScratchIMD imd;\n    IMDPumpRevenueSplitter splitter;\n    address owner = makeAddr(\"owner\");\n    address payout = makeAddr(\"payout\");\n    address developer = makeAddr(\"developer\");\n\n    function setUp() public {\n        imd = new ScratchIMD();\n        splitter = new IMDPumpRevenueSplitter(owner, payout, address(imd));\n        vm.prank(owner);\n        splitter.setDeveloper(developer);\n    }\n\n    function test_setAllocationDoesNotLeaveDeliveredRevenueToBeResplit() public {\n        imd.mint(address(splitter), 1_000e18);\n        assertEq(splitter.undistributedRevenue(), 1_000e18);\n\n        vm.prank(owner);\n        try splitter.setAllocation(2_000, 500, 3_500, 2_000, 2_000) {\n            // The setter went through: everything delivered under the 15% split must be settled under it.\n            assertEq(splitter.undistributedRevenue(), 0, \"revenue delivered under the old split is still waiting\");\n            assertEq(imd.balanceOf(developer), 150e18, \"developer did not receive the 15% in force when revenue arrived\");\n        } catch {\n            // The setter refused to run while revenue is waiting: also acceptable.\n            assertEq(splitter.undistributedRevenue(), 1_000e18);\n        }\n    }\n\n    function test_setDeveloperDoesNotLeaveDeliveredRevenueForTheNewRecipient() public {\n        imd.mint(address(splitter), 1_000e18);\n        address newDeveloper = makeAddr(\"newDeveloper\");\n        vm.prank(owner);\n        try splitter.setDeveloper(newDeveloper) {\n            assertEq(splitter.undistributedRevenue(), 0, \"revenue delivered before the re-point is still waiting\");\n            assertEq(imd.balanceOf(developer), 150e18, \"old developer did not receive the share earned before the re-point\");\n            assertEq(imd.balanceOf(newDeveloper), 0, \"new developer received revenue earned before the re-point\");\n        } catch {\n            assertEq(splitter.undistributedRevenue(), 1_000e18);\n        }\n    }\n\n    /// The race itself, as it plays out on the current code: the developer's distribute() lands\n    /// before the owner's cut and takes 150e18 instead of 50e18.\n    function test_showRace_developerFrontRunsCut() public {\n        imd.mint(address(splitter), 1_000e18);\n        vm.prank(developer);\n        splitter.distribute();\n        vm.prank(owner);\n        splitter.setAllocation(2_000, 500, 3_500, 2_000, 2_000);\n        assertEq(imd.balanceOf(developer), 150e18);\n        assertEq(imd.balanceOf(payout), 650e18); // treasury 250 + ops 200 + nft 200\n    }\n}","reproduction":"State: owner has called setDeveloper(D); allocation is the launch split (developer 1,500 bps). The operator transfers 1,000e18 IMD to the splitter (undistributedRevenue() == 1,000e18). The owner broadcasts setAllocation(2000, 500, 3500, 2000, 2000), cutting the developer to 5%. D broadcasts distribute() with a higher priority fee and it executes first. Actual: D receives 150e18 and the payout address 650e18 (treasury 250 + operations 200 + NFT workers 200); the cut applies only to later revenue. Owner's expectation: D receives 50e18 and the treasury bucket 350e18. Same with setDeveloper(newD): if the setter lands first newD receives the 150e18 earned while D was the recipient; if distribute() lands first the compromised key D receives it. forge test --match-path test/scratch/AllocationRace.t.sol on this tree: test_setAllocationDoesNotLeaveDeliveredRevenueToBeResplit FAILS ('revenue delivered under the old split is still waiting: 1000000000000000000000 != 0'), test_setDeveloperDoesNotLeaveDeliveredRevenueForTheNewRecipient FAILS likewise; test_showRace_developerFrontRunsCut passes and shows the 150e18/650e18 outcome.","severity":"low","snippet":"        Allocation memory a = allocation;","title":"Revenue already delivered is split by the allocation and recipients current when the permissionless distribute() runs, so an owner setter and a recipient's distribute() race in the mempool for in-flig"},{"citation":"resolved","description":"From audit_permissions (reported medium; recalibrated to low). Every distribute() while burnSink is unset commits burnShare to pendingBurn (line 287), adds it to totalDistributed and emits RevenueDistributed(..., burnShare, ...) and BurnDeferred: the amount is publicly accounted as 'IMD buyback and burn'. setBurnSink (lines 252-257) refuses only address(0) and address(this), so the first setBurnSink decides after the fact where everything already allocated to burning goes, including the owner's own wallet, and _executeBurn (lines 306-313) then increments totalBurned and emits BurnExecuted(sink, amount) for that transfer. This contradicts two statements the code and README make about the trust model: that the owner 'can only re-point future flows' (header line 31; README trust section) and that 'every burn emits BurnExecuted ... so it can be verified on chain' (header line 26-27). pendingBurn is past, already-accounted revenue, and BurnExecuted/totalBurned attest whatever destination the owner named. Severity low rather than medium: the actor is the owner (the requester), who already controls the whole allocation and all recipients for future revenue, so this is a privileged power that the documentation misdescribes rather than a bypass, and the IMD project's burn address is deliberately not hard-coded (README 'IMD has no burn function'). Minimal fixes that preserve the owner-configured design: make the sink a one-shot setter or a constructor argument so the destination is fixed before revenue is accounted, and/or require `sink.code.length == 0` plus a small fixed allowlist (0x000000000000000000000000000000000000dEaD, 0x...0001) if the requester wants the record to be verifiable; otherwise correct the header comment and README to say that pendingBurn and the BurnExecuted/totalBurned record are owner-directed, not independent verification.","line":253,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"State: fresh IMDPumpRevenueSplitter(owner, payout, IMD) with burnSink == address(0). 1) Transfer 1,000e18 IMD to the splitter; anyone calls distribute(): RevenueDistributed(caller, 1000e18, 200e18, 150e18, 250e18, 200e18, 200e18) and BurnDeferred(200e18); pendingBurn == 200e18. 2) Repeat: pendingBurn == 400e18, totalDistributed == 2,000e18. 3) owner calls setBurnSink(owner): succeeds. 4) Anyone calls burnPending(). Expected per README/brief: 400e18 IMD reach a burn destination. Actual: IMD.balanceOf(owner) == 400e18, totalBurned == 400e18, BurnExecuted(owner, 400e18) emitted, splitter balance 0. Verified with test/scratch/BurnSinkSweep.t.sol (test_setBurnSinkSweepsAlreadyAllocatedPendingBurnToOwner passes on this tree, demonstrating the behaviour).","severity":"low","snippet":"        if (sink == address(0)) revert ZeroAddress();\n        if (sink == address(this)) revert SelfRecipient();","title":"setBurnSink accepts any non-zero, non-self address and retroactively redirects pendingBurn already accounted as burn share; the contract then records that payout as a verified burn (BurnExecuted, tota"}],"hash":"817957abaa34bfb83b4b9122a58943722e98c18e0875e189acc1fca79a729ac6","nodeId":"73f4895d-470f-4e72-895d-0a054f63f22d","outcome":"completed","summary":"Review complete. The findings file is written and valid, no source files were changed, and only `test/scratch/` holds my reproductions.\n\n**Outcome: 3 findings kept from 7 specialist reports, 17 of 17 entry points answered, plus 5 invariant and manifest rows.**\n\nFindings written to `.imd-findings.json`:\n\n1. **Medium: unguarded `renounceOwnership()`.** Merged from three specialists reporting the same root cause. The inherited one-step renounce has no guard, so renouncing while the burn sink is unset strands the burn share forever and freezes every setting. Reproduced with the economics specialist's proof, which fails on the current code for exactly the stated reason. Attached as proof.\n\n2. **Low: in-flight revenue is split by whatever allocation and recipients are current at `distribute()` time.** Merged from three specialists. A recipient about to lose share can front-run the owner's setter. I replaced the specialist's proof with a fix-agnostic one that passes whether the author settles first or rejects setters while revenue waits. Verified failing on the current code.\n\n3. **Low: `setBurnSink` retroactively redirects already-accounted burn share and records it as a verified burn.** Downgraded from the permissions specialist's medium. The actor is the owner, who already controls all future flows, so it is a misdocumented privileged power rather than a bypass. Reproduced with a passing demonstration test.\n\nDropped: the slither strict-equality lead (a plain zero check), the aderyn storage-array lead (a storage-to-memory copy), the counter-pollution note (documented behaviour), and centralization and literal-size notes.\n\nOwn pass found nothing new. I verified the real IMD token on mainnet is a LayerZero OFT standard ERC20 with no burn, pause, blacklist, fee or proxy, so the splitter's dependency assumptions hold. The manifest matches the constructors and supply, and the existing suite passes with 109 tests.","treeHash":null,"usage":{"cachedInputTokens":364317,"chain":[{"calls":17,"chainId":1,"failed":0,"own":11,"public":0,"refused":6}],"cost":{"source":"runtime","usdMicros":2573226},"inputTokens":162,"model":"claude-fable-5-1","outputTokens":19491,"runtime":"claude","turns":15,"wallClockMs":336165}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7f1dec5ffcbde1d8","findings":[{"citation":"resolved","description":"Invariant / Flow Gap (execution x first principles). The operator delivers eligible net revenue with a plain IMD transfer and the split is only fixed when distribute() runs; _distribute() reads `allocation` (line 277) and `recipients` (line 294) at that moment. setAllocation, setDeveloper, setTreasury, setOperations and setNftWorkers change those values without first settling revenue that is already waiting. distribute() is callable by anyone and reverts on nothing, so any party with a stake in the current split (the developer wallet, the treasury, an operations or NFT-worker contract) can watch the owner's setter in the mempool and call distribute() first, taking the share in force before the change. The reverse also holds: revenue that arrived while the developer was entitled to 15% can be re-split at 5% by a setter that lands before distribute(), or paid to a newly re-pointed recipient who had not earned it. All five buckets start at the same payout address, so the race has no effect until the owner has re-pointed at least one of them; after that it moves value between identifiable parties against the owner's announced intent. The README's advice to 'announce before calling setAllocation' does not close the window, because owner and operator transactions are separate and revenue may arrive between them. Minimal fix that preserves the design: in setAllocation and the four recipient setters, call `if (undistributedRevenue() > 0) _distribute();` before writing the new value, so everything delivered under the old split is settled under it.","line":277,"path":"src/IMDPumpRevenueSplitter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IMDPumpRevenueSplitter} from \"src/IMDPumpRevenueSplitter.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// Revenue that has already been delivered to the splitter is split by whatever allocation and\n/// recipients are current when `distribute()` runs, not by the ones in force when it arrived.\n/// Because `distribute()` is permissionless, a recipient about to lose share can front-run the\n/// owner's setter and lock in the old split. The fix is for the setters to settle waiting revenue\n/// first; this test fails until they do.\ncontract AllocationRaceTest is Test {\n    ScratchIMD imd;\n    IMDPumpRevenueSplitter splitter;\n    address owner = makeAddr(\"owner\");\n    address payout = makeAddr(\"payout\");\n    address developer = makeAddr(\"developer\");\n    address treasury = makeAddr(\"treasury\");\n\n    function setUp() public {\n        imd = new ScratchIMD();\n        splitter = new IMDPumpRevenueSplitter(owner, payout, address(imd));\n        vm.startPrank(owner);\n        splitter.setDeveloper(developer);\n        splitter.setTreasury(treasury);\n        vm.stopPrank();\n    }\n\n    function test_setAllocationSettlesRevenueThatArrivedUnderTheOldSplit() public {\n        // The operator delivers 1,000 IMD of net revenue under the launch split (developer 15%).\n        imd.mint(address(splitter), 1_000e18);\n        assertEq(splitter.undistributedRevenue(), 1_000e18);\n\n        // The owner cuts the developer to 5% at the sustainability review.\n        vm.prank(owner);\n        splitter.setAllocation(2_000, 500, 3_500, 2_000, 2_000);\n\n        // Revenue that arrived under the 15% split must have been settled under it: nothing may\n        // still be waiting to be re-split under the new numbers.\n        assertEq(splitter.undistributedRevenue(), 0, \"revenue delivered under the old split is still waiting\");\n        assertEq(imd.balanceOf(developer), 150e18, \"developer did not receive the 15% in force when revenue arrived\");\n    }\n\n    function test_setDeveloperSettlesRevenueThatArrivedForTheOldRecipient() public {\n        imd.mint(address(splitter), 1_000e18);\n        address newDeveloper = makeAddr(\"newDeveloper\");\n        vm.prank(owner);\n        splitter.setDeveloper(newDeveloper);\n        assertEq(splitter.undistributedRevenue(), 0, \"revenue delivered before the re-point is still waiting\");\n        assertEq(imd.balanceOf(developer), 150e18, \"old developer did not receive the share earned before the re-point\");\n        assertEq(imd.balanceOf(newDeveloper), 0, \"new developer received revenue earned before the re-point\");\n    }\n}","reproduction":"State: owner has run setDeveloper(developer) and setTreasury(treasury); the operator has transferred 1,000e18 IMD to the splitter (undistributedRevenue() == 1,000e18) under the launch allocation (developer 1,500 bps). Step 1: owner submits setAllocation(2000, 500, 3500, 2000, 2000) cutting the developer to 5%. Step 2: developer submits distribute() with higher gas and it executes first. Actual: developer receives 150e18 and the treasury 250e18; the owner's cut only applies to later revenue. Expected: revenue delivered under the 15% split is settled before the split changes, or the 5% split applies and the developer receives 50e18 with 350e18 to the treasury; either way the outcome should not depend on who wins the mempool race. Same with setDeveloper(newDeveloper): if the setter lands first the new recipient receives the 150e18 earned by the old one. The attached test asserts that after each setter nothing is left waiting and the old recipient holds 150e18; it fails on the current code (1,000e18 still waiting, developer holds 0) and passes once the setters settle first.","severity":"low","snippet":"        Allocation memory a = allocation;","title":"Revenue already delivered is split by the allocation and recipients current at distribute() time, so a recipient can front-run setAllocation/setX with the permissionless distribute() and lock in the o"},{"citation":"resolved","description":"Invariant (strand value across a transition) / Economic Security (break a dependency so a flow is blocked forever). The contract inherits Ownable2Step and therefore the one-step renounceOwnership() from Ownable, without an override. The design relies on the owner to set burnSink before any burn can leave (line 163 `if (burnSink == address(0)) revert BurnSinkNotConfigured();`), and recoverToken() deliberately refuses the revenue token (line 262). If the owner renounces before the sink is configured, which the README itself invites ('Use a multisig for the owner key', 'set the burn sink promptly after launch') and which a 'no admin, fully decentralised' step commonly does, then: setBurnSink can never be called, burnPending() reverts forever, distribute() keeps adding 20% of every delivery to pendingBurn, and that IMD stays in the contract with totalBurned == 0 and no BurnExecuted event. The brief's 'verified burns' can then never be produced, and the only way to stop the loss is to stop delivering revenue to the splitter. The same renounce also freezes any wrongly configured recipient forever, which the README lists as a risk the setters exist to correct. Ownership transfer is two-step precisely to avoid an irreversible mistake, but renouncing is one step and unconditional. Minimal fix preserving the design: override renounceOwnership() to revert while burnSink == address(0) (or to revert always, if the requester never intends to give up control of the allocation).","line":33,"path":"src/IMDPumpRevenueSplitter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IMDPumpRevenueSplitter} from \"src/IMDPumpRevenueSplitter.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// `renounceOwnership` is inherited unguarded. If it is called while `burnSink` is still the zero\n/// address, no one can ever set the sink, `burnPending` reverts forever, `recoverToken` refuses\n/// IMD, and the 20% burn share of every past and future distribution is locked in the splitter\n/// without a `BurnExecuted` record. The fix is to refuse renouncing while the sink is unset (or to\n/// disable renouncing); this test fails until it does.\ncontract RenounceStrandsBurnTest is Test {\n    ScratchIMD imd;\n    IMDPumpRevenueSplitter splitter;\n    address owner = makeAddr(\"owner\");\n    address payout = makeAddr(\"payout\");\n\n    function setUp() public {\n        imd = new ScratchIMD();\n        splitter = new IMDPumpRevenueSplitter(owner, payout, address(imd));\n    }\n\n    function test_renounceOwnershipRefusedWhileBurnSinkUnset() public {\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        assertEq(splitter.pendingBurn(), 20e18);\n        assertEq(splitter.burnSink(), address(0));\n\n        vm.prank(owner);\n        vm.expectRevert();\n        splitter.renounceOwnership();\n\n        assertEq(splitter.owner(), owner, \"ownership was renounced with the burn share unroutable\");\n    }\n\n    function test_renouncedSplitterLocksBurnShareForever() public {\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        vm.prank(owner);\n        (bool renounced,) = address(splitter).call(abi.encodeWithSignature(\"renounceOwnership()\"));\n        if (!renounced || splitter.owner() != address(0)) return; // fixed: renounce was refused, nothing to show\n\n        // Nobody can set the sink any more.\n        vm.expectRevert();\n        splitter.setBurnSink(address(0xdead));\n        // The burn cannot run.\n        vm.expectRevert(IMDPumpRevenueSplitter.BurnSinkNotConfigured.selector);\n        splitter.burnPending();\n        // And every future distribution adds to the locked amount.\n        imd.mint(address(splitter), 100e18);\n        splitter.distribute();\n        assertEq(splitter.pendingBurn(), 40e18);\n        assertEq(imd.balanceOf(address(splitter)), 40e18);\n        assertEq(splitter.totalBurned(), 0, \"nothing was ever verifiably burned\");\n        fail();\n    }\n}","reproduction":"State: splitter deployed with burnSink == address(0) (the launch state); the operator has delivered 100e18 IMD and anyone called distribute(), so pendingBurn == 20e18 and the splitter holds 20e18. Call: owner calls renounceOwnership(). Actual: succeeds, owner() == address(0). Afterwards setBurnSink(0x...dEaD) reverts with OwnableUnauthorizedAccount from every caller, burnPending() reverts with BurnSinkNotConfigured, and a further 100e18 delivery plus distribute() raises pendingBurn to 40e18 held in the contract with totalBurned == 0. Expected: renouncing while the burn share has no destination is refused (revert), so the 20% stream always has a route out and every burn is recorded by BurnExecuted. The attached test expects the revert and fails on the current code ('next call did not revert as expected'); the second test walks the locked state and fails deliberately whenever the renounce goes through.","severity":"low","snippet":"contract IMDPumpRevenueSplitter is Ownable2Step, ReentrancyGuard {","title":"renounceOwnership() is inherited unguarded: renouncing while burnSink is unset permanently locks the 20% burn share of every past and future distribution in the splitter with no BurnExecuted record"}],"hash":"a7a48384523a64ddecbe45529c1bc90c46dc3375a6d8d9d8117dfe97f62b5eb2","nodeId":"2abdc50a-2437-425a-9927-013cb75dab24","outcome":"completed","summary":"The economics audit is complete. The findings file at the repository root holds two substantiated findings and a coverage row for every one of the 17 listed entry points plus four invariant and dependency rows. No source file was changed.\n\n**What I found**\n\n- **Low: allocation and recipient setters race the permissionless distribute().** Revenue already delivered is split by whatever allocation and recipients are current when distribute runs, not those in force when it arrived. A recipient about to lose share can front-run setAllocation or a re-point and lock in the old split, and the reverse ordering pays a new recipient revenue earned by the old one. Reproduction: 1,000 IMD waiting, owner cuts the developer from 15% to 5%, developer calls distribute first and takes 150 instead of 50. The fix is to settle waiting revenue inside the five setters. Proof test attached.\n- **Low: renounceOwnership is inherited unguarded.** Renouncing while the burn sink is unset locks the 20% burn share of every past and future distribution in the splitter forever, since nobody can set the sink, burnPending reverts, and recoverToken refuses IMD. No BurnExecuted record is ever produced. Fix is to refuse renouncing while burnSink is zero. Proof test attached.\n\nBoth proof tests fail on the current code and pass on a patched copy I built outside the repository, so they hold the author to the stated fixes.\n\n**What holds**\n\n- Revenue conservation, rounding dust to treasury, the balance-equals-pendingBurn coupling, and the lifetime totals all hold under the launch split and arbitrary allocations, including 1 wei inputs.\n- depositAndDistribute splits the measured balance rather than the argument, so a short delivery cannot over-pay.\n- I fetched the real IMD token's mainnet bytecode. It is a LayerZero OFT on OpenZeppelin ERC-20 with no pause, blacklist, fee or burn selector, so IMD behaviour cannot block or distort distribution.\n- The token is a plain fixed-supply ERC-20 that satisfies every launch-floor flow.\n- Both static-analysis leads are false positives and are recorded as such in the coverage.\n\n**Trust assumptions, not defects:** the burn sink is any non-zero, non-self address the owner chooses, and the owner can re-point all five buckets and change the split. Both are documented in the README and are the requested design.\n\n**Not reached:** nothing within the assigned area. No launch.json exists yet, so manifest constructor arguments for the splitter were not reviewed.","treeHash":null,"usage":{"cachedInputTokens":640192,"cost":{"source":"runtime","usdMicros":3215141},"inputTokens":258,"model":"claude-fable-5-1","outputTokens":24226,"runtime":"claude","turns":22,"wallClockMs":396616}},{"artifacts":[],"attempt":1,"bundleHash":"09520bdaca68fd6d49f9db1ea52dc29e5537fdf611d18dd8b5fba5dce4c64870","device":"6ea221d017084baf","findings":[],"hash":"c24ff1b5b434bcefd3029ad38229b028f6d8431061260eb68622fb9a76d7d88e","nodeId":"a04459ea-832f-4529-8410-b7f20151e1ab","outcome":"completed","summary":"Added failure-path, fuzz, wallet-gas, and invariant tests under `test/`.\n\nVerification passed with the specified seed: **109 tests**, including **65,536 invariant calls**. `forge build` also passes.\n\nNo confirmed implementation defects found. Production contracts and configuration remain unchanged.","treeHash":"8c177d6262ea28e7b7bc255ecb729304ba45e354","usage":{"cachedInputTokens":1782528,"cost":{"source":"estimated","usdMicros":4458828},"inputTokens":98125,"model":"gpt-6-astra","outputTokens":33901,"runtime":"codex","turns":7,"wallClockMs":843992}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dc7103e773ea194a","findings":[{"citation":"resolved","description":"The splitter inherits OpenZeppelin's renounceOwnership() unchanged (Ownable.sol:76, reachable as the ABI entry point IMDPumpRevenueSplitter.renounceOwnership()). Nothing in the contract makes it conditional on the burn sink being configured or pendingBurn being zero. Until setBurnSink is called, every distribution parks 20% of revenue in pendingBurn (line 287) and the only exits for that IMD are burnPending()/_distribute(), both gated by `burnSink != address(0)` (lines 163 and 301), and recoverToken refuses the revenue token (line 262). After the owner renounces, no address can ever call setBurnSink, so the accumulated burn share can never reach a burn destination, BurnExecuted is never emitted for it, and the brief's 'verified burn' never happens for that IMD. The same call also freezes the allocation and the four recipients forever, which conflicts with the brief's planned three-month sustainability review. This is a one-way owner action, so severity is low, but it is a concrete state from which the contract cannot recover. Minimal fix that preserves the design: override renounceOwnership() to revert (ownership moves only via the two-step transfer), or require `burnSink != address(0) && pendingBurn == 0` before calling super.renounceOwnership().","line":33,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"State: splitter deployed with owner O, payout P, revenue token IMD; burnSink unset (its launch state). 1) Operator transfers 100e18 IMD to the splitter and anyone calls distribute(): P receives 80e18, pendingBurn == 20e18, BurnDeferred(20e18). 2) O calls renounceOwnership(): owner() == address(0). 3) Now setBurnSink(0xdead) reverts OwnableUnauthorizedAccount for every caller including O; burnPending() reverts BurnSinkNotConfigured; distribute() reverts NothingToDistribute; recoverToken(IMD, O, 20e18) reverts (OwnableUnauthorizedAccount, and RevenueTokenNotRecoverable even for an owner). Expected: the 20e18 burn share eventually reaches a burn address and emits BurnExecuted. Actual: 20e18 IMD sits in the splitter forever with no code path that can move it. Verified with a Foundry test against MockIMD (test passes, showing the stranded state).","severity":"low","snippet":"contract IMDPumpRevenueSplitter is Ownable2Step, ReentrancyGuard {","title":"renounceOwnership() while burnSink is unset permanently strands the deferred 20% burn share and freezes every setting"},{"citation":"resolved","description":"By design anyone may call distribute(), and the README's primary flow is 'plain transfer followed by distribute'. Two consequences the operator must plan around: (a) once IMD is in the contract, the owner cannot reconfigure before it is split, because a third party can call distribute() first and the split uses whatever allocation and recipients are current (lines 277, 294). An owner who notices a wrong recipient after the transfer but before distribute cannot prevent payment to the wrong address. (b) totalDistributed, totalToDeveloper, totalBurned and RevenueDistributed count every IMD anyone sends, so they are not a measure of 'eligible net platform revenue'; a stranger can inflate them at will. Neither is a loss of funds or a bypass, so this is informational. Mitigations that preserve the permissionless design: prefer depositAndDistribute for every deposit so configuration always precedes the money, and document that the counters measure IMD processed, not revenue.","line":156,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"(a) State: recipients.developer == D_old, operator has transferred 1,000e18 IMD to the splitter and the owner intends to call setDeveloper(D_new) before distribution. Input: attacker calls distribute() first. Actual: D_old receives 150e18; the subsequent setDeveloper only affects later revenue. Expected by the operator: D_new paid. (b) Input: attacker transfers 1e18 IMD to the splitter and calls distribute(). Actual: totalDistributed increases by 1e18 and RevenueDistributed is emitted for revenue that is not platform revenue.","severity":"info","snippet":"    function distribute() external nonReentrant returns (uint256 revenue) {","title":"Permissionless distribute() lets any third party lock in the current allocation/recipients for IMD already waiting, and pollutes the lifetime counters"}],"hash":"d7161d0fa2b820dff954c809c96317e535e64f71e530989746eb7d0d3c874dfc","nodeId":"01466fcd-efb2-419a-b430-7460318ec274","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` with 2 findings and 21 coverage rows, and no repository file was changed.\n\n**What I checked.** I traced both contracts against the Execution Trace, Periphery and First Principles guides, ran the project's 56 tests (all pass), and inspected the splitter's one external dependency, the mainnet IMD token. Its runtime holds only OpenZeppelin ERC20 and LayerZero OFT selectors, with no tax, limit, pause or blacklist, and no proxy slot. A mainnet fork test confirmed the splitter pays exactly 80% to the payout address and burns exactly 20% to a dead address with the real token.\n\n**Findings.**\n- **Low:** the inherited `renounceOwnership()` is unconditional. If the owner renounces before setting the burn sink, the accumulated 20% burn share can never leave the contract, since both burn paths require a sink, the setter is now unreachable, and `recoverToken` refuses IMD. It also freezes the allocation and recipients, which conflicts with the planned three-month review. Reproduced with a Foundry test. Fix: override it to revert, or require the sink to be set and the pending burn to be zero.\n- **Info:** `distribute()` is permissionless by design, so a third party can lock in the current recipients for IMD already sitting in the contract before the owner reconfigures, and can inflate the lifetime counters by sending dust. Mitigation is operational: use `depositAndDistribute` so configuration always precedes money.\n\n**What holds.** All 17 entry points received a row. The share math sums exactly to revenue with dust to the treasury and cannot underflow. Balance-based accounting keeps the splitter's IMD balance at or above `pendingBurn` on every path. Both static-analysis leads are benign. The token is an unmodified OpenZeppelin ERC20 with a self-only burn, meeting the launch floor.\n\n**For the manifest step:** the splitter's `owner_` must be `$requester`, never `$factory`, or the Owners floor rejects it.","treeHash":null,"usage":{"cachedInputTokens":553741,"chain":[{"calls":64,"chainId":1,"failed":0,"own":50,"public":0,"refused":14}],"cost":{"source":"runtime","usdMicros":2840519},"inputTokens":258,"model":"claude-fable-5-1","outputTokens":22996,"runtime":"claude","turns":16,"wallClockMs":415119}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fbcdfc017217af1f","findings":[{"citation":"resolved","description":"Trust-gap (access x asymmetry, 'retroactive sweep'). Every distribute() while burnSink is unset commits burnShare to pendingBurn, increments totalDistributed and emits RevenueDistributed(..., burnShare, ...) and BurnDeferred: the amount is publicly accounted as 'IMD buyback and burn'. setBurnSink (src/IMDPumpRevenueSplitter.sol:252-257) only refuses address(0) and address(this); it accepts the owner's own wallet, a recipient, or any EOA. The first setBurnSink therefore decides, after the fact, where everything already allocated to burning goes, and _executeBurn (lines 306-313) then increments totalBurned and emits BurnExecuted(sink, amount) for that transfer. The README's trust statement says the owner 'can only re-point future flows' and that 'every burn emits BurnExecuted ... so it can be verified on chain'; neither holds: pendingBurn is past, already-accounted revenue, and BurnExecuted/totalBurned attest whatever destination the owner named. By contrast _checkRecipient and setBurnSink both guard against the splitter itself, but nothing guards that the sink is actually a burn. Actor: owner. Unprivileged amplifier: burnPending() is permissionless, so once the sink is set anyone (including the owner from a fresh address) flushes the swept amount. The economic weight is the whole 20% burn allocation accumulated before the sink is configured, which the README expects to happen 'promptly after launch' but nothing enforces. Minimal fix preserving the owner-configured design: constrain the sink to a canonical, provably unspendable address (e.g. require sink == 0x000000000000000000000000000000000000dEaD or a small fixed allowlist, and sink.code.length == 0), or make the sink a constructor argument / one-shot setter so the destination is fixed before any revenue is accounted. If the requester wants to keep a freely re-pointable sink, the README trust section must say that pendingBurn and the BurnExecuted/totalBurned record are owner-controlled, not verification.","line":253,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"State: fresh IMDPumpRevenueSplitter(owner, payout, IMD) with burnSink == 0. 1) mint/transfer 1_000e18 IMD to the splitter; anyone calls distribute(): emits RevenueDistributed(caller, 1000e18, 200e18, 150e18, 250e18, 200e18, 200e18) and BurnDeferred(200e18); pendingBurn == 200e18, totalDistributed == 1000e18. 2) repeat once: pendingBurn == 400e18, totalDistributed == 2000e18. 3) owner calls setBurnSink(owner) -> succeeds (only 0 and self are refused). 4) anyone calls burnPending(): expected (per README/brief) 400e18 IMD leave to a burn destination; actual IMD.balanceOf(owner) == 400e18, totalBurned == 400e18, BurnExecuted(owner, 400e18) emitted. Verified with a Foundry test on this tree (test_setBurnSinkSweepsAlreadyAllocatedPendingBurn): all four assertions pass on the current code.","severity":"medium","snippet":"        if (sink == address(0)) revert ZeroAddress();\n        if (sink == address(this)) revert SelfRecipient();\n        emit BurnSinkUpdated(burnSink, sink);\n        burnSink = sink;","title":"setBurnSink accepts any address and retroactively redirects pendingBurn already recorded as burn share; the contract then attests the payout as a verified burn"},{"citation":"resolved","description":"Access-control / asymmetry. The contract inherits Ownable.renounceOwnership() (lib/openzeppelin-contracts/contracts/access/Ownable.sol, `function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); }`) and does not override it. Every other path that could leave the system without a valid address is guarded: _checkRecipient (lines 335-338) and setBurnSink (line 253) refuse address(0), and recoverToken (line 262) refuses the revenue token. renounceOwnership has no such guard, so a single owner call while burnSink == address(0) makes the sink unsettable forever. Consequences: (a) the current pendingBurn can never leave (burnPending reverts BurnSinkNotConfigured; recoverToken refuses REVENUE_TOKEN; _distribute excludes pendingBurn); (b) every future distribute() still pays 80% out and strands the 20% burn share, so the lock grows with all future revenue; (c) the four recipient addresses are frozen forever, so a lost or compromised recipient key cannot be re-pointed. The brief's central promise, 'IMD purchases and verified burns', becomes permanently unfulfillable while the contract keeps accepting revenue and reporting BurnDeferred. The README documents only two-step transfer and never mentions renouncing; renouncing ownership is a common 'decentralization' step a requester may take in good faith. Minimal fix preserving the design: override renounceOwnership to revert (the owner role is required by the brief's three-month review), or at minimum require burnSink != address(0) before renouncing. Note that acceptOwnership/transferOwnership are unaffected and behave correctly.","line":33,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"State: splitter deployed with burnSink == 0; 1_000e18 IMD transferred in; anyone calls distribute() -> pendingBurn == 200e18. Then owner calls renounceOwnership(): succeeds, owner() == address(0). Expected: the contract should refuse to leave the burn destination permanently unsettable (or refuse to renounce at all). Actual: setBurnSink(0xdead) from the former owner reverts OwnableUnauthorizedAccount; burnPending() reverts BurnSinkNotConfigured; recoverToken(IMD, payout, 200e18) reverts RevenueTokenNotRecoverable even when called from address(0); a further 1_000e18 transferred in and distribute() leaves pendingBurn == 400e18 and IMD.balanceOf(splitter) == 400e18 with no function able to move it. Verified with a Foundry test on this tree (test_renounceBeforeSinkStrandsBurnShareForever).","severity":"medium","snippet":"contract IMDPumpRevenueSplitter is Ownable2Step, ReentrancyGuard {","title":"renounceOwnership is inherited unguarded: renouncing before setBurnSink permanently locks the 20% burn share of all past and future revenue in the contract"},{"citation":"resolved","description":"Asymmetry (admin-function variant sandwiched by a user-side call) / trust gap (access x economics). Revenue reaches the splitter either atomically (depositAndDistribute) or by a plain IMD transfer followed by distribute(), which the README presents as the first-class flow. Any IMD sitting in the contract between those two steps is split under whatever allocation and recipients are in storage at the moment distribute() runs, and distribute() is callable by anyone (line 156). setAllocation (lines 202-219) and setDeveloper/setTreasury/setOperations/setNftWorkers (lines 222-247) change storage without first settling the revenue that was already waiting under the announced terms. So whoever is disadvantaged by a pending owner change (a developer whose share is being cut, a recipient address that is about to be replaced because its key is compromised) can watch the mempool and call distribute() ahead of the owner's transaction, paying itself the old share of the entire waiting balance. The owner has no way to make the outcome deterministic from inside the contract. Impact is bounded by the revenue waiting at the moment of the change and the actor must be a current recipient (or a rogue key holding one), hence low. Minimal fixes preserving the permissionless design: document that revenue must only ever arrive through depositAndDistribute (never transfer-then-distribute) so nothing is ever waiting, and/or have the owner's setters revert when undistributedRevenue() > 0 so a change cannot be made while revenue is in flight (the owner then flushes first, deliberately, under the old terms). Restricting distribute() to an operator role would also close it but changes the agreed 'anyone may call distribute' behaviour, so that is a scope decision for the requester.","line":156,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"State: owner has set developer = D (15%). Operator transfers 1_000_000e18 IMD to the splitter and broadcasts nothing else yet. Owner broadcasts setAllocation(2000, 500, 3500, 2000, 2000) (developer 15% -> 5%). D sees it and submits distribute() with a higher priority fee. Order: distribute() then setAllocation. Expected (owner intent): D receives 50_000e18. Actual: D receives 150_000e18 (IMD.balanceOf(D) == 150_000e18); the treasury bucket receives 250_000e18 instead of 350_000e18. Same mechanism with setDeveloper(newD) when D's key is compromised: the old key calls distribute() first and receives 150_000e18. Verified with a Foundry test on this tree (test_recipientFrontRunsAllocationCut).","severity":"low","snippet":"    function distribute() external nonReentrant returns (uint256 revenue) {\n        revenue = _distribute();\n    }","title":"Permissionless distribute() lets a recipient whose share is about to be cut or re-pointed front-run setAllocation / set* to lock in the old split for revenue already waiting"}],"hash":"e744bf2862b2e55ee529003d6ccb9903f970c6065636957612fe384bf094f661","nodeId":"701b2700-68ce-44e8-a5e2-5c87b13d056e","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` with every snippet verified against the tree and all 17 entry points answered.\n\n**What I found (permissions area: Access Control, Trust Gap, Asymmetry)**\n\n- **Medium: `setBurnSink` is a retroactive sweep.** It refuses only the zero address and the splitter itself. Every `distribute()` before the sink is set publicly accounts 20% as burn share in `pendingBurn`, `totalDistributed` and the `RevenueDistributed` event. The owner's first `setBurnSink` then decides where all of that goes, and `burnPending()` records the transfer as `BurnExecuted`/`totalBurned`. Reproduced: two distributions of 1,000 IMD, `setBurnSink(owner)`, `burnPending()` puts 400 IMD in the owner's wallet with `totalBurned == 400e18`. Fix options: constrain the sink to a canonical dead address, or fix it at construction.\n- **Medium: inherited `renounceOwnership` has no guard.** Every other path refuses the zero address, but renouncing before the sink is set makes it unsettable forever. Reproduced: `pendingBurn` of 200 IMD becomes unreachable, and each later `distribute()` strands another 20%. Fix: override to revert, or require `burnSink != 0` first.\n- **Low: permissionless `distribute()` races owner setters.** Revenue sent by plain transfer and not yet distributed is split under whatever is in storage when `distribute()` runs. A recipient whose share is being cut can front-run `setAllocation` and lock in the old split. Reproduced with a developer taking 150,000 instead of 50,000 IMD on 1,000,000 waiting.\n\n**What holds.** The token is an unmodified OpenZeppelin ERC-20 with self-only `burn`, no owner and no exemption logic. Two-step ownership, all recipient setters, `recoverToken` and `depositAndDistribute` have correct guards. I diffed every vendored OpenZeppelin file the contracts inherit against the upstream v5.7.0 tag and they are byte-identical. The IMD mainnet token is a LayerZero OFT with no pause, blacklist or burn, so the IMD owner cannot freeze distributions.\n\n**Static-analysis leads.** The Slither strict-equality on `revenue == 0` and the Aderyn storage-to-memory struct copy at the preview function are both benign and were not reported.\n\n**Not reached.** `launch.json` is not in the tree, so the actual `owner_` constructor argument the manifest will pass could not be checked. Only the three permission guides were applied in depth. The scratch test directory was removed, so no repository file is changed.","treeHash":null,"usage":{"cachedInputTokens":876119,"cost":{"source":"runtime","usdMicros":3952151},"inputTokens":290,"model":"claude-fable-5-1","outputTokens":26796,"runtime":"claude","turns":26,"wallClockMs":434920}},{"artifacts":[],"attempt":1,"bundleHash":"d60f7448352b69c764db83cf211fcca0dd4eb535c07ec9c986d966cead12bd74","device":"9abb45864e8fdce1","findings":[],"hash":"f1c65a463586ac99900a8e2fba36c2b5bd6b246443c2d3a8359a700982c1b0dd","nodeId":"d421d380-6fe8-4923-8b29-a4b23ae2a8e2","outcome":"completed","summary":"All three findings are answered and the work is verified.\n\n**Reproduced and fixed (medium, the proof):** `renounceOwnership()` succeeded with the burn sink unset, stranding the 20% share forever. The splitter now overrides it to revert with a `RenounceDisabled` error, so ownership moves only by two-step transfer. The reviewer's proof passes on the result, and three new tests cover the disabled call, the sink still being settable afterwards, and the stranger path.\n\n**Reproduced and fixed (low, setter race):** revenue waiting in the contract was split by whatever allocation and recipients were in storage when a permissionless `distribute()` ran, so an owner setter and a recipient could race over it. Every setter now settles waiting revenue under the old terms before writing, and is reentrancy-guarded since it now makes transfers. I chose settling over refusing while revenue waits, because the latter lets anyone block every setter with a 1 wei transfer. I confirmed from IMD's verified source that it is a LayerZero OFT with plain ERC-20 transfers, so a settle inside a setter cannot deadlock a re-point. Eight new tests cover the report's exact 150/650 scenario, each setter, the no-revenue case, reentrancy, and a rejected allocation.\n\n**Reproduced, fixed by documentation (low, burn sink):** the owner can name any sink and the first one receives the already-accrued pending burn, which the header and README had misdescribed as owner-proof verification. The code is unchanged for this finding. The header, the setter docstring and the README now state plainly that the record attests a transfer to an owner-chosen sink, and that verification means checking that sink against the IMD project's burn address.\n\nResults on this tree, with the verifier's fuzz seed:\n\n| Check | Result |\n| --- | --- |\n| forge build | passes |\n| forge fmt --check | passes |\n| forge test | 72 passed, 0 failed, including the proof under test/scratch |\n\nChanged files: `src/IMDPumpRevenueSplitter.sol`, `test/IMDPumpRevenueSplitter.t.sol`, `README.md`, plus `.imd-responses.json` at the repository root with the three verdicts. Nothing was committed, as the brief did not ask for it.","treeHash":"965bbeccc9d4ebe1b8245fc5678719c3b6ef5837","usage":{"cachedInputTokens":871096,"cost":{"source":"runtime","usdMicros":3133775},"inputTokens":386,"model":"claude-fable-5-1","outputTokens":25649,"runtime":"claude","turns":31,"wallClockMs":401289}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9e51ef2afd7c2af8","findings":[{"citation":"resolved","description":"Carried from write_foundry_tests (reported low; recalibrated to info). Every owner setter (setAllocation line 226, setDeveloper 241, setTreasury 250, setOperations 259, setNftWorkers 268, setBurnSink 284) now calls _settleWaitingRevenue() before writing, and that runs the full _distribute() whose SafeERC20 payouts (lines 333-336, 355) revert if the revenue token refuses a current recipient or the current sink. If that ever happened, distribute() would already be blocked, and the setter that repairs it would be blocked too as soon as undistributedRevenue() > 0, which anyone can force with a one-unit transfer; IMD cannot leave by recoverToken (line 300). Reproduced on this tree with the tester's proof (a mock ERC-20 whose _update reverts for one address): test_ownerCanRepointARefusedRecipientWhileRevenueIsWaiting fails with 'refused'. Why info: REVENUE_TOKEN is immutable and the manifest binds it to 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7. I checked that contract on Ethereum mainnet: it is not a proxy (EIP-1967 implementation slot is zero, 23 KB of code at the address itself), its selector set is a LayerZero OFT plus plain OpenZeppelin ERC-20 (owner, setPeer, setDelegate, name/symbol setters) with no paused(), pause(), blacklist or blocklist selectors, and _checkRecipient refuses address(0) and the splitter itself. A plain ERC-20 transfer to any other address cannot revert, and the shares always sum to balance minus pendingBurn, so the only failing transfer the token can produce is unreachable. The trade-off the author chose (settle rather than refuse while revenue waits) is sound for this token. If the author wants belt-and-braces, settling with try/catch and leaving the revenue waiting on failure preserves the design; but this is not a defect of the deployed configuration and should not hold the launch.","line":345,"path":"src/IMDPumpRevenueSplitter.sol","reproduction":"Only with a revenue token that can refuse a recipient (not the bound IMD token). State: IMDPumpRevenueSplitter(OWNER, OWNER, refusingToken); owner setTreasury(T); token starts refusing T; anyone transfers 1 unit of the token to the splitter so undistributedRevenue() == 1. Call: owner setTreasury(T2). Expected: T2 becomes treasury and the waiting unit can be distributed. Actual: setTreasury(T2) reverts with the token's error 'refused' because _settleWaitingRevenue() -> _distribute() pays the waiting unit to T first; distribute() reverts identically. Verified by copying .imd/reads/proofs/Proof_1a26c03588b8.t.sol to test/scratch/ and running forge test --match-path: FAIL 'refused'. With the real IMD token (cast code / cast selectors on mainnet, 2026-11-14: no pause, blocklist or proxy) no recipient transfer can revert, so the state is not reachable.","severity":"info","snippet":"    function _settleWaitingRevenue() private {\n        if (undistributedRevenue() > 0) _distribute();\n    }","title":"Settle-first setters (added for bbf01ab7) cannot re-point a recipient the revenue token refuses while any revenue is waiting; unreachable with the bound IMD token, so no change is required for this la"}],"hash":"f37f4593c199328bafc71767afa332fff231930f23998d3785bb7703c9e0213c","nodeId":"73f4895d-470f-4e72-895d-0a054f63f22d","outcome":"completed","summary":"The review is written to `.imd-findings.json`. All three findings from my earlier round are settled as fixed, and nothing new blocks the launch.\n\n**Settled findings**\n\n- **renounceOwnership strands the burn share (medium).** Fixed. The function is overridden to revert with a new error, so ownership moves only through the two-step transfer. My attached proof passes both tests on the revised code.\n- **Setter versus distribute() mempool race (low).** Fixed. All six owner setters now settle any waiting revenue under the old terms before writing. My proof and the economics specialist's proof both pass, and the author's eight new tests covering this pass.\n- **setBurnSink retroactively directs pendingBurn (low).** Settled as a documentation fix, as the author answered. The header, the setter docstring and the README now say the burn record attests a transfer to the owner-chosen sink, not that the sink is a burn address. The code behaviour is an owner power, recorded as a trust assumption.\n\n**The tester's new finding**\n\nThe independent tester reported that the settle-first setters cannot re-point a recipient the revenue token refuses while revenue is waiting. The proof does fail on this code, with a mock token that rejects one address. I kept it as info only, because the precondition is unreachable with the token the splitter binds. I checked the IMD token on Ethereum mainnet directly. It is not a proxy, and its selector set is a LayerZero OFT with plain OpenZeppelin ERC-20 transfers and no pause or blocklist function. A transfer to any non-zero address cannot revert, and the setters already refuse the zero address and the splitter itself. The finding states explicitly that no change is required for this launch.\n\n**Other checks**\n\n- The full test suite passes on this tree, 131 tests, with only the tester's mock-token proof failing as described.\n- Both static-analysis leads are false positives. The strict equality is a revert guard on a computed difference, and the memory copy of the allocation struct feeds a pure function.\n- The protected floor tests could not run here because they import the verifier's harness, which is not in this tree. The verifier runs them.\n- All sixteen entry points are answered in the coverage record, plus rows for the three settled findings, the two static leads and the deployment inputs.\n\nNothing under `test/scratch/` is kept, and no project file was changed.","treeHash":null,"usage":{"cachedInputTokens":507916,"cost":{"source":"runtime","usdMicros":2067149},"inputTokens":258,"model":"claude-fable-5-1","outputTokens":11402,"runtime":"claude","turns":14,"wallClockMs":231296}}],"verification":[{"checks":[{"durationMs":1421,"exitCode":0,"name":"build","output":"Compiling 42 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.33s\nCompiler run successful!\n","passed":true},{"durationMs":120,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2396222)\n[PASS] test_deployWithConfig() (gas: 4252796)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 631.70µs (429.72µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83320, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 4.64ms (6.11ms CPU time)\n\nRan 39 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 512186, ~: 517796)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 544987, ~: 560783)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267645)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 91823)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 531642)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2070594)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1446322)\n[PASS] test_constructorRejectsZeroOwner() (gas: 193184)\n[PASS] test_constructorRejectsZeroPayout() (gas: 241091)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 241096)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 342019)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327386)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34871)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120847)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 558681)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 343831)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 458392)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270231)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42309)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245406)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1914455)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138879)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398484)\n[PASS] test_previewMatchesDistribution() (gas: 255431)\n[PASS] test_recipientSetters() (gas: 235485)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112681)\n[PASS] test_recoverStrayToken() (gas: 733461)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384120)\n[PASS] test_recoverValidation() (gas: 700794)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2328822)\n[PASS] test_rejectsEther() (gas: 38522)\n[PASS] test_roundingDustGoesToTreasury() (gas: 478926)\n[PASS] test_runtimeFitsEip170() (gas: 5035)\n[PASS] test_setAllocation() (gas: 293784)\n[PASS] test_setAllocationOnlyOwner() (gas: 35929)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 63267)\n[PASS] test_settersOnlyOwner() (gas: 146156)\n[PASS] test_settersRejectZeroAndSelf() (gas: 183774)\nSuite result: ok. 39 passed; 0 failed; 0 skipped; finished in 19.06ms (43.38ms CPU time)\n\nRan 3 test suites in 19.86ms (24.33ms CPU time): 56 tests passed, 0 failed, 0 skipped (56 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.renounceOwnership()\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":163,\"foundry.toml\":27,\"launch.json\":33,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":339,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueSplitter.t.sol\":509,\"test/IMDPumpToken.t.sol\":168,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0cefa8cde67b81e3a0c46ed5d4a8671a02f23667b25882530de1355b0dc76168","verifiedTreeHash":"1b44206de8c9637bc4b12611c975f4bd6c7d25cf","verifierVersion":"0.1.0+a490e3c0"},{"checks":[{"durationMs":1471,"exitCode":0,"name":"build","output":"Compiling 42 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.39s\nCompiler run successful!\n","passed":true},{"durationMs":122,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2396222)\n[PASS] test_deployWithConfig() (gas: 4252796)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 878.90µs (606.64µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83320, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 4.35ms (5.50ms CPU time)\n\nRan 39 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 512186, ~: 517796)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 544987, ~: 560783)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267645)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 91823)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 531642)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2070594)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1446322)\n[PASS] test_constructorRejectsZeroOwner() (gas: 193184)\n[PASS] test_constructorRejectsZeroPayout() (gas: 241091)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 241096)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 342019)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327386)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34871)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120847)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 558681)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 343831)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 458392)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270231)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42309)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245406)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1914455)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138879)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398484)\n[PASS] test_previewMatchesDistribution() (gas: 255431)\n[PASS] test_recipientSetters() (gas: 235485)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112681)\n[PASS] test_recoverStrayToken() (gas: 733461)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384120)\n[PASS] test_recoverValidation() (gas: 700794)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2328822)\n[PASS] test_rejectsEther() (gas: 38522)\n[PASS] test_roundingDustGoesToTreasury() (gas: 478926)\n[PASS] test_runtimeFitsEip170() (gas: 5035)\n[PASS] test_setAllocation() (gas: 293784)\n[PASS] test_setAllocationOnlyOwner() (gas: 35929)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 63267)\n[PASS] test_settersOnlyOwner() (gas: 146156)\n[PASS] test_settersRejectZeroAndSelf() (gas: 183774)\nSuite result: ok. 39 passed; 0 failed; 0 skipped; finished in 17.00ms (37.86ms CPU time)\n\nRan 3 test suites in 17.87ms (22.23ms CPU time): 56 tests passed, 0 failed, 0 skipped (56 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.renounceOwnership()\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":163,\"foundry.toml\":27,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":339,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueSplitter.t.sol\":509,\"test/IMDPumpToken.t.sol\":168,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":771,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/IMDPumpRevenueSplitter.sol:273: IMDPumpRevenueSplitter._distribute() (src/IMDPumpRevenueSplitter.sol#273-304) uses a dangerous strict equality:","passed":true},{"durationMs":250,"exitCode":0,"name":"aderyn","output":"[high] storage-array-memory-edit at src/IMDPumpRevenueSplitter.sol:188: Storage Array Edited with Memory\n[low] centralization-risk at src/IMDPumpRevenueSplitter.sol:33: Centralization Risk (8 places)\n[low] large-numeric-literal at src/IMDPumpRevenueSplitter.sol:37: Large Numeric Literal (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"649ed9d59cd6e8e3a5a1c1b600c6ec0ff18b61d17b89953ee008f8c807f39cb9","verifiedTreeHash":"9faa7e48f27d0392de7582ba7bd3d15c407af80e","verifierVersion":"0.1.0+a490e3c0"},{"checks":[{"durationMs":3338,"exitCode":0,"name":"build","output":"Compiling 49 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.23s\nCompiler run successful!\n","passed":true},{"durationMs":11056,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2429316)\n[PASS] test_deployWithConfig() (gas: 4316561)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.14ms (806.28µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83242, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 33.06ms (35.77ms CPU time)\n\nRan 11 tests for test/IMDPumpWalletGas.t.sol:IMDPumpWalletGasTest\n[PASS] test_walletGas_burnPendingKeepsUnallocatedRevenue() (gas: 2581667)\n[PASS] test_walletGas_depositWithFiniteAllowanceAndRevenueAlreadyWaiting() (gas: 4736481)\n[PASS] test_walletGas_depositWithInfiniteAllowanceAndDeferredBurn() (gas: 6719400)\n[PASS] test_walletGas_distributeFlushesOldAndNewBurnsToAnExistingRecipient() (gas: 3858819)\n[PASS] test_walletGas_distributeWithAllPayoutsAndImmediateBurn() (gas: 6839315)\n[PASS] test_walletGas_distributeWithDeferredBurn() (gas: 6087212)\n[PASS] test_walletGas_dustAndAllBurnAllocation() (gas: 5695450)\n[PASS] test_walletGas_noReturnRevenueAndSharedPayoutAddress() (gas: 11351156)\n[PASS] test_walletGas_recoverEntireStrayBalance() (gas: 2253376)\n[PASS] test_walletGas_recoverTokenWithoutReturnData() (gas: 1615671)\n[PASS] test_walletGas_settersSettleWaitingRevenue() (gas: 23260953)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 49.11ms (57.43ms CPU time)\n\nRan 50 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 551104, ~: 555617)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 590649, ~: 608716)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267578)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 101892)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 541562)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2103663)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1479369)\n[PASS] test_constructorRejectsZeroOwner() (gas: 195604)\n[PASS] test_constructorRejectsZeroPayout() (gas: 243467)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 243494)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 341926)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327360)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34831)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120850)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 596581)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 351392)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 468381)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270119)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42269)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245291)\n[PASS] test_everyRecipientSetterSettlesWaitingRevenueFirst() (gas: 855170)\n[PASS] test_invalidAllocationIsRefusedBeforeSettling() (gas: 123505)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1947476)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138857)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398320)\n[PASS] test_previewMatchesDistribution() (gas: 255405)\n[PASS] test_recipientSetters() (gas: 285986)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112615)\n[PASS] test_recoverStrayToken() (gas: 733439)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384098)\n[PASS] test_recoverValidation() (gas: 700728)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2361775)\n[PASS] test_rejectsEther() (gas: 38478)\n[PASS] test_renounceOwnershipIsDisabledEvenWithBurnSinkSet() (gas: 83788)\n[PASS] test_renounceOwnershipIsDisabledWhileBurnSinkUnset() (gas: 419995)\n[PASS] test_renounceOwnershipRefusesStrangers() (gas: 13483)\n[PASS] test_roundingDustGoesToTreasury() (gas: 516769)\n[PASS] test_runtimeFitsEip170() (gas: 5058)\n[PASS] test_setAllocation() (gas: 303852)\n[PASS] test_setAllocationOnlyOwner() (gas: 41110)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 73453)\n[PASS] test_setAllocationSettlesWaitingRevenueUnderTheOldSplit() (gas: 733400)\n[PASS] test_setBurnSinkSettlesWaitingRevenueUnderThePreviousSink() (gas: 355655)\n[PASS] test_setBurnSinkWithNoPreviousSinkKeepsTheWaitingBurnSharePending() (gas: 370775)\n[PASS] test_setDeveloperSettlesWaitingRevenueToThePreviousDeveloper() (gas: 683174)\n[PASS] test_setterSettlementIsReentrancyGuarded() (gas: 2382066)\n[PASS] test_settersOnlyOwner() (gas: 171731)\n[PASS] test_settersRejectZeroAndSelf() (gas: 219557)\n[PASS] test_settersWithNothingWaitingDistributeNothing() (gas: 543542)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 50.74ms (79.46ms CPU time)\n\nRan 16 tests for test/IMDPumpTokenProperties.t.sol:IMDPumpTokenPropertiesTest\n[PASS] testFuzz_burnOnlyRemovesCallersBalance(uint256,uint256) (runs: 1000, μ: 147899, ~: 148788)\n[PASS] testFuzz_finiteAllowanceTracksOnlySuccessfulSpending(uint256,uint256) (runs: 1000, μ: 196934, ~: 198809)\n[PASS] testFuzz_roundTripTransfersLoseNoTokens(uint256) (runs: 1000, μ: 116020, ~: 115827)\n[PASS] test_approvalOverwritesInsteadOfAddingAndRejectsZeroSpender() (gas: 119686)\n[PASS] test_burnEntireSupplyLeavesNoPositiveTransferOrBurnPossible() (gas: 153022)\n[PASS] test_delegatedSelfTransferConsumesOnlyAllowance() (gas: 111593)\n[PASS] test_evenAnApprovedSpenderCannotBurnOnBehalfOfHolder() (gas: 105148)\n[PASS] test_failedTransferFromRollsBackAllowanceAndBalances() (gas: 171252)\n[PASS] test_infiniteAllowanceSurvivesRepeatedTransfersAndCanBeRevoked() (gas: 229836)\n[PASS] test_maximumAmountsCannotTransferOrBurnBeyondSupply() (gas: 75359)\n[PASS] test_selfTransferOfFullBalancePreservesBalanceAndSupply() (gas: 48143)\n[PASS] test_supplyCanPassThroughLaunchRecipientsWithoutTaxes() (gas: 346167)\n[PASS] test_zeroBurnEmitsBothEventsWithoutChangingSupply() (gas: 54814)\n[PASS] test_zeroTransferFromAnEmptyAccountEmitsTransfer() (gas: 64239)\n[PASS] test_zeroTransferFromNeedsNoAllowanceButStillRejectsZeroAddresses() (gas: 127662)\n[PASS] test_zeroTransferToZeroAddressIsStillInvalid() (gas: 37043)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 55.74ms (158.77ms CPU time)\n\nRan 29 tests for test/IMDPumpRevenueProperties.t.sol:IMDPumpRevenuePropertiesTest\n[PASS] testFuzz_invalidAllocationNeverChangesConfiguration(uint16[5]) (runs: 1000, μ: 89214, ~: 89213)\n[PASS] testFuzz_previewAndPayoutsRespectRoundingBounds(uint128,uint16[4]) (runs: 1000, μ: 477126, ~: 510083)\n[PASS] test_allRecipientSettersRejectTheSplitterItselfWithoutChangingConfig() (gas: 146517)\n[PASS] test_allRevenueCanBeAssignedToEachIndividualBucket() (gas: 10197959)\n[PASS] test_allocationValidationUsesWideSumAndPreservesConfig() (gas: 150664)\n[PASS] test_bubbledReentryDuringDepositRollsBackPullAndAllowsLaterRetry() (gas: 3411963)\n[PASS] test_burnPendingDoesNotConsumeNewUnallocatedRevenue() (gas: 691214)\n[PASS] test_cancelledOwnershipCannotBeAccepted() (gas: 120577)\n[PASS] test_changingBurnDestinationAffectsOnlyUnsentBurns() (gas: 622392)\n[PASS] test_deferredBurnSurvivesAllocationChangeAndNewRevenue() (gas: 708442)\n[PASS] test_eachFailedPayoutRollsBackTheWholeDistributionAndCanBeRetried() (gas: 35843222)\n[PASS] test_everyGuardedEntryRejectsCrossFunctionReentryFromEachTransferPhase() (gas: 28517897)\n[PASS] test_everySetterSettlesWaitingRevenueToTheOldTermsEvenWhenUnchanged() (gas: 15967183)\n[PASS] test_failedBurnDoesNotEraseTheLiabilityOrClaimABurn() (gas: 3168348)\n[PASS] test_failedIncomingTransferRestoresDepositorAndAllowance() (gas: 6028434)\n[PASS] test_falseReturningRecoveryRollsBackStrayTransfer() (gas: 874156)\n[PASS] test_insufficientApprovalCannotUseAnotherDepositorsApproval() (gas: 190182)\n[PASS] test_insufficientDepositorBalanceRestoresAllowanceAndAllAccounting() (gas: 630792)\n[PASS] test_newRecipientsOnlyReceiveLaterDistributions() (gas: 663054)\n[PASS] test_noReturnCurrencySupportsPullDeferredBurnAndLaterFlush() (gas: 2419334)\n[PASS] test_oneWeiIsPaidToTreasuryAndCannotBeDistributedTwice() (gas: 262039)\n[PASS] test_pendingOwnerHasNoPowerAndReplacementRevokesTheFirstNominee() (gas: 296346)\n[PASS] test_recoveryAboveStrayBalanceCannotMoveRevenue() (gas: 1299890)\n[PASS] test_recoveryOfCodeLessTokenFailsInsteadOfClaimingSuccess() (gas: 40153)\n[PASS] test_rejectedSetterInputDoesNotSettleWaitingRevenue() (gas: 454657)\n[PASS] test_renounceIsRefusedAndLeavesOwnerNomineeAndDistributionIntact() (gas: 537578)\n[PASS] test_setterSettlementCannotBeReenteredByThePayoutToken() (gas: 12710283)\n[PASS] test_strangerDustCannotRedirectASettingChangeAndIsPaidUnderOldTerms() (gas: 526016)\n[PASS] test_zeroDepositDoesNotDistributeAlreadyWaitingRevenue() (gas: 292738)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 56.04ms (133.61ms CPU time)\n\nRan 1 test for test/invariant/IMDPumpTokenInvariant.t.sol:IMDPumpTokenInvariantTest\n[PASS]\nIMDPumpTokenInvariantTest invariants:\n[PASS] invariant_balancesAndAllowancesMatchTheIndependentLedger\n[PASS] invariant_supplyEqualsAllBalancesAndOnlyHolderBurnsReduceIt\n[PASS] invariant_validCallsSucceedAndInvalidCallsFailForTheExpectedReason\n IMDPumpTokenInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭---------------------+----------------------+-------+---------+----------╮\n| Contract            | Selector             | Calls | Reverts | Discards |\n+=========================================================================+\n| IMDPumpTokenHandler | approve              | 3971  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | approveAndSpend      | 4106  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | burn                 | 4157  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | overspend            | 3995  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | revokeAndTrySpending | 4093  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transfer             | 4242  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transferFrom         | 4088  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transferToZero       | 4116  | 0       | 0        |\n╰---------------------+----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.55s (9.55s CPU time)\n\nRan 1 test for test/invariant/IMDPumpRevenueInvariant.t.sol:IMDPumpRevenueInvariantTest\n[PASS]\nIMDPumpRevenueInvariantTest invariants:\n[PASS] invariant_allRevenueIsEitherPaidOwedOrWaiting\n[PASS] invariant_expectedOutcomesAndPerPayoutRoundingAlwaysHold\n[PASS] invariant_lifetimeCountersMatchIndependentCredits\n[PASS] invariant_onlyAcceptedOwnerActionsChangeConfiguration\n[PASS] invariant_recoveringOtherTokensKeepsTheirSeparateLedger\n IMDPumpRevenueInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-----------------------+-----------------------+-------+---------+----------╮\n| Contract              | Selector              | Calls | Reverts | Discards |\n+============================================================================+\n| IMDPumpRevenueHandler | burnPending           | 3621  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | configure             | 3672  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | deposit               | 3672  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | distribute            | 3610  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | donate                | 3711  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | recoverStray          | 3700  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | rejectRevenueRecovery | 3636  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | rejectZeroDeposit     | 3508  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | unauthorized          | 3638  | 0       | 0        |\n╰-----------------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.92s (10.92s CPU time)\n\nRan 8 test suites in 10.93s (20.72s CPU time): 125 tests passed, 0 failed, 0 skipped (125 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":191,\"foundry.toml\":27,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":382,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueProperties.t.sol\":678,\"test/IMDPumpRevenueSplitter.t.sol\":680,\"test/IMDPumpToken.t.sol\":168,\"test/IMDPumpTokenProperties.t.sol\":242,\"test/IMDPumpWalletGas.t.sol\":201,\"test/TESTING.md\":39,\"test/helpers/WalletGas.sol\":76,\"test/invariant/IMDPumpRevenueInvariant.t.sol\":433,\"test/invariant/IMDPumpTokenInvariant.t.sol\":177,\"test/mocks/AdversarialRevenueToken.sol\":92,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"7450abc4432698d33c72fc473b84e878f1e7c195932ded50a9626db66917a445","verifiedTreeHash":"3cf7ba80e16ef538716fe8713f6eb83bc7ad021b","verifierVersion":"0.1.0+a490e3c0"},{"checks":[{"durationMs":1522,"exitCode":0,"name":"build","output":"Compiling 42 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.43s\nCompiler run successful!\n","passed":true},{"durationMs":128,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2429316)\n[PASS] test_deployWithConfig() (gas: 4316561)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.08ms (680.92µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83245, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 4.00ms (5.66ms CPU time)\n\nRan 50 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 550800, ~: 555623)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 595177, ~: 608770)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267578)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 101892)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 541562)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2103663)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1479369)\n[PASS] test_constructorRejectsZeroOwner() (gas: 195604)\n[PASS] test_constructorRejectsZeroPayout() (gas: 243467)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 243494)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 341926)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327360)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34831)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120850)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 596581)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 351392)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 468381)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270119)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42269)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245291)\n[PASS] test_everyRecipientSetterSettlesWaitingRevenueFirst() (gas: 855170)\n[PASS] test_invalidAllocationIsRefusedBeforeSettling() (gas: 123505)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1947476)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138857)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398320)\n[PASS] test_previewMatchesDistribution() (gas: 255405)\n[PASS] test_recipientSetters() (gas: 285986)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112615)\n[PASS] test_recoverStrayToken() (gas: 733439)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384098)\n[PASS] test_recoverValidation() (gas: 700728)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2361775)\n[PASS] test_rejectsEther() (gas: 38478)\n[PASS] test_renounceOwnershipIsDisabledEvenWithBurnSinkSet() (gas: 83788)\n[PASS] test_renounceOwnershipIsDisabledWhileBurnSinkUnset() (gas: 419995)\n[PASS] test_renounceOwnershipRefusesStrangers() (gas: 13483)\n[PASS] test_roundingDustGoesToTreasury() (gas: 516769)\n[PASS] test_runtimeFitsEip170() (gas: 5058)\n[PASS] test_setAllocation() (gas: 303852)\n[PASS] test_setAllocationOnlyOwner() (gas: 41110)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 73453)\n[PASS] test_setAllocationSettlesWaitingRevenueUnderTheOldSplit() (gas: 733400)\n[PASS] test_setBurnSinkSettlesWaitingRevenueUnderThePreviousSink() (gas: 355655)\n[PASS] test_setBurnSinkWithNoPreviousSinkKeepsTheWaitingBurnSharePending() (gas: 370775)\n[PASS] test_setDeveloperSettlesWaitingRevenueToThePreviousDeveloper() (gas: 683174)\n[PASS] test_setterSettlementIsReentrancyGuarded() (gas: 2382066)\n[PASS] test_settersOnlyOwner() (gas: 171731)\n[PASS] test_settersRejectZeroAndSelf() (gas: 219557)\n[PASS] test_settersWithNothingWaitingDistributeNothing() (gas: 543542)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 22.40ms (56.06ms CPU time)\n\nRan 3 test suites in 23.11ms (27.48ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":36,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":191,\"foundry.toml\":27,\"launch.json\":33,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":382,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueSplitter.t.sol\":680,\"test/IMDPumpToken.t.sol\":168,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"801ad669b5cbc71e3c182bbb306e705aac39de8faeb738e82b6a24854dd5ab82","verifiedTreeHash":"5f5e646d274b41db0029a9b29bff112ff65272a9","verifierVersion":"0.1.0+a490e3c0"},{"checks":[{"durationMs":3097,"exitCode":0,"name":"build","output":"Compiling 49 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.00s\nCompiler run successful!\n","passed":true},{"durationMs":10748,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2396222)\n[PASS] test_deployWithConfig() (gas: 4252796)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.65ms (462.95µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83317, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 49.63ms (32.53ms CPU time)\n\nRan 10 tests for test/IMDPumpWalletGas.t.sol:IMDPumpWalletGasTest\n[PASS] test_walletGas_burnPendingKeepsUnallocatedRevenue() (gas: 2580504)\n[PASS] test_walletGas_depositWithFiniteAllowanceAndRevenueAlreadyWaiting() (gas: 4726488)\n[PASS] test_walletGas_depositWithInfiniteAllowanceAndDeferredBurn() (gas: 6756784)\n[PASS] test_walletGas_distributeFlushesOldAndNewBurnsToAnExistingRecipient() (gas: 3870254)\n[PASS] test_walletGas_distributeWithAllPayoutsAndImmediateBurn() (gas: 6822821)\n[PASS] test_walletGas_distributeWithDeferredBurn() (gas: 6047673)\n[PASS] test_walletGas_dustAndAllBurnAllocation() (gas: 5723796)\n[PASS] test_walletGas_noReturnRevenueAndSharedPayoutAddress() (gas: 11298319)\n[PASS] test_walletGas_recoverEntireStrayBalance() (gas: 2252225)\n[PASS] test_walletGas_recoverTokenWithoutReturnData() (gas: 1614339)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 53.47ms (42.49ms CPU time)\n\nRan 39 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 512492, ~: 517796)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 540944, ~: 560730)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267645)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 91823)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 531642)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2070594)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1446322)\n[PASS] test_constructorRejectsZeroOwner() (gas: 193184)\n[PASS] test_constructorRejectsZeroPayout() (gas: 241091)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 241096)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 342019)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327386)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34871)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120847)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 558681)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 343831)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 458392)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270231)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42309)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245406)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1914455)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138879)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398484)\n[PASS] test_previewMatchesDistribution() (gas: 255431)\n[PASS] test_recipientSetters() (gas: 235485)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112681)\n[PASS] test_recoverStrayToken() (gas: 733461)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384120)\n[PASS] test_recoverValidation() (gas: 700794)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2328822)\n[PASS] test_rejectsEther() (gas: 38522)\n[PASS] test_roundingDustGoesToTreasury() (gas: 478926)\n[PASS] test_runtimeFitsEip170() (gas: 5035)\n[PASS] test_setAllocation() (gas: 293784)\n[PASS] test_setAllocationOnlyOwner() (gas: 35929)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 63267)\n[PASS] test_settersOnlyOwner() (gas: 146156)\n[PASS] test_settersRejectZeroAndSelf() (gas: 183774)\nSuite result: ok. 39 passed; 0 failed; 0 skipped; finished in 54.06ms (106.19ms CPU time)\n\nRan 16 tests for test/IMDPumpTokenProperties.t.sol:IMDPumpTokenPropertiesTest\n[PASS] testFuzz_burnOnlyRemovesCallersBalance(uint256,uint256) (runs: 1000, μ: 147879, ~: 148788)\n[PASS] testFuzz_finiteAllowanceTracksOnlySuccessfulSpending(uint256,uint256) (runs: 1000, μ: 196908, ~: 198809)\n[PASS] testFuzz_roundTripTransfersLoseNoTokens(uint256) (runs: 1000, μ: 116104, ~: 115827)\n[PASS] test_approvalOverwritesInsteadOfAddingAndRejectsZeroSpender() (gas: 119686)\n[PASS] test_burnEntireSupplyLeavesNoPositiveTransferOrBurnPossible() (gas: 153022)\n[PASS] test_delegatedSelfTransferConsumesOnlyAllowance() (gas: 111593)\n[PASS] test_evenAnApprovedSpenderCannotBurnOnBehalfOfHolder() (gas: 105148)\n[PASS] test_failedTransferFromRollsBackAllowanceAndBalances() (gas: 171252)\n[PASS] test_infiniteAllowanceSurvivesRepeatedTransfersAndCanBeRevoked() (gas: 229836)\n[PASS] test_maximumAmountsCannotTransferOrBurnBeyondSupply() (gas: 75359)\n[PASS] test_selfTransferOfFullBalancePreservesBalanceAndSupply() (gas: 48143)\n[PASS] test_supplyCanPassThroughLaunchRecipientsWithoutTaxes() (gas: 346167)\n[PASS] test_zeroBurnEmitsBothEventsWithoutChangingSupply() (gas: 54814)\n[PASS] test_zeroTransferFromAnEmptyAccountEmitsTransfer() (gas: 64239)\n[PASS] test_zeroTransferFromNeedsNoAllowanceButStillRejectsZeroAddresses() (gas: 127662)\n[PASS] test_zeroTransferToZeroAddressIsStillInvalid() (gas: 37043)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 54.17ms (152.85ms CPU time)\n\nRan 25 tests for test/IMDPumpRevenueProperties.t.sol:IMDPumpRevenuePropertiesTest\n[PASS] testFuzz_invalidAllocationNeverChangesConfiguration(uint16[5]) (runs: 1000, μ: 84077, ~: 84076)\n[PASS] testFuzz_previewAndPayoutsRespectRoundingBounds(uint128,uint16[4]) (runs: 1000, μ: 458613, ~: 492333)\n[PASS] test_allRecipientSettersRejectTheSplitterItselfWithoutChangingConfig() (gas: 131172)\n[PASS] test_allRevenueCanBeAssignedToEachIndividualBucket() (gas: 9732274)\n[PASS] test_allocationValidationUsesWideSumAndPreservesConfig() (gas: 135319)\n[PASS] test_bubbledReentryDuringDepositRollsBackPullAndAllowsLaterRetry() (gas: 3338497)\n[PASS] test_burnPendingDoesNotConsumeNewUnallocatedRevenue() (gas: 659549)\n[PASS] test_cancelledOwnershipCannotBeAccepted() (gas: 120577)\n[PASS] test_changingBurnDestinationAffectsOnlyUnsentBurns() (gas: 604794)\n[PASS] test_deferredBurnSurvivesAllocationChangeAndNewRevenue() (gas: 611935)\n[PASS] test_eachFailedPayoutRollsBackTheWholeDistributionAndCanBeRetried() (gas: 35009932)\n[PASS] test_everyGuardedEntryRejectsCrossFunctionReentryFromEachTransferPhase() (gas: 27765344)\n[PASS] test_failedBurnDoesNotEraseTheLiabilityOrClaimABurn() (gas: 3084956)\n[PASS] test_failedIncomingTransferRestoresDepositorAndAllowance() (gas: 5881706)\n[PASS] test_falseReturningRecoveryRollsBackStrayTransfer() (gas: 874134)\n[PASS] test_insufficientApprovalCannotUseAnotherDepositorsApproval() (gas: 190223)\n[PASS] test_insufficientDepositorBalanceRestoresAllowanceAndAllAccounting() (gas: 630904)\n[PASS] test_newRecipientsOnlyReceiveLaterDistributions() (gas: 622784)\n[PASS] test_noReturnCurrencySupportsPullDeferredBurnAndLaterFlush() (gas: 2335924)\n[PASS] test_oneWeiIsPaidToTreasuryAndCannotBeDistributedTwice() (gas: 262142)\n[PASS] test_pendingOwnerHasNoPowerAndReplacementRevokesTheFirstNominee() (gas: 275979)\n[PASS] test_recoveryAboveStrayBalanceCannotMoveRevenue() (gas: 1299961)\n[PASS] test_recoveryOfCodeLessTokenFailsInsteadOfClaimingSuccess() (gas: 40131)\n[PASS] test_renouncingConfiguredOwnershipClearsNomineeAndLeavesPublicDistributionLive() (gas: 532677)\n[PASS] test_zeroDepositDoesNotDistributeAlreadyWaitingRevenue() (gas: 292779)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 54.14ms (97.13ms CPU time)\n\nRan 1 test for test/invariant/IMDPumpTokenInvariant.t.sol:IMDPumpTokenInvariantTest\n[PASS]\nIMDPumpTokenInvariantTest invariants:\n[PASS] invariant_balancesAndAllowancesMatchTheIndependentLedger\n[PASS] invariant_supplyEqualsAllBalancesAndOnlyHolderBurnsReduceIt\n[PASS] invariant_validCallsSucceedAndInvalidCallsFailForTheExpectedReason\n IMDPumpTokenInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭---------------------+----------------------+-------+---------+----------╮\n| Contract            | Selector             | Calls | Reverts | Discards |\n+=========================================================================+\n| IMDPumpTokenHandler | approve              | 3971  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | approveAndSpend      | 4106  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | burn                 | 4157  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | overspend            | 3995  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | revokeAndTrySpending | 4093  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transfer             | 4242  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transferFrom         | 4088  | 0       | 0        |\n|---------------------+----------------------+-------+---------+----------|\n| IMDPumpTokenHandler | transferToZero       | 4116  | 0       | 0        |\n╰---------------------+----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.61s (9.60s CPU time)\n\nRan 1 test for test/invariant/IMDPumpRevenueInvariant.t.sol:IMDPumpRevenueInvariantTest\n[PASS]\nIMDPumpRevenueInvariantTest invariants:\n[PASS] invariant_allRevenueIsEitherPaidOwedOrWaiting\n[PASS] invariant_expectedOutcomesAndPerPayoutRoundingAlwaysHold\n[PASS] invariant_lifetimeCountersMatchIndependentCredits\n[PASS] invariant_onlyAcceptedOwnerActionsChangeConfiguration\n[PASS] invariant_recoveringOtherTokensKeepsTheirSeparateLedger\n IMDPumpRevenueInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-----------------------+-----------------------+-------+---------+----------╮\n| Contract              | Selector              | Calls | Reverts | Discards |\n+============================================================================+\n| IMDPumpRevenueHandler | burnPending           | 3621  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | configure             | 3672  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | deposit               | 3672  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | distribute            | 3610  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | donate                | 3711  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | recoverStray          | 3700  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | rejectRevenueRecovery | 3636  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | rejectZeroDeposit     | 3508  | 0       | 0        |\n|-----------------------+-----------------------+-------+---------+----------|\n| IMDPumpRevenueHandler | unauthorized          | 3638  | 0       | 0        |\n╰-----------------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.63s (10.62s CPU time)\n\nRan 8 test suites in 10.63s (20.50s CPU time): 109 tests passed, 0 failed, 0 skipped (109 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.renounceOwnership()\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":163,\"foundry.toml\":27,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":339,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueProperties.t.sol\":549,\"test/IMDPumpRevenueSplitter.t.sol\":509,\"test/IMDPumpToken.t.sol\":168,\"test/IMDPumpTokenProperties.t.sol\":242,\"test/IMDPumpWalletGas.t.sol\":177,\"test/TESTING.md\":35,\"test/helpers/WalletGas.sol\":76,\"test/invariant/IMDPumpRevenueInvariant.t.sol\":409,\"test/invariant/IMDPumpTokenInvariant.t.sol\":177,\"test/mocks/AdversarialRevenueToken.sol\":92,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c24ff1b5b434bcefd3029ad38229b028f6d8431061260eb68622fb9a76d7d88e","verifiedTreeHash":"8c177d6262ea28e7b7bc255ecb729304ba45e354","verifierVersion":"0.1.0+a490e3c0"},{"checks":[{"durationMs":1666,"exitCode":0,"name":"build","output":"Compiling 42 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.56s\nCompiler run successful!\n","passed":true},{"durationMs":142,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constantsMatchTheBrief() (gas: 2429316)\n[PASS] test_deployWithConfig() (gas: 4316561)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 885.84µs (620.28µs CPU time)\n\nRan 15 tests for test/IMDPumpToken.t.sol:IMDPumpTokenTest\n[PASS] testFuzz_transfersAreUntaxed(address,uint256) (runs: 256, μ: 83245, ~: 83396)\n[PASS] test_burnMoreThanBalanceReverts() (gas: 34964)\n[PASS] test_holderCanBurnOwnTokens() (gas: 112939)\n[PASS] test_metadata() (gas: 28938)\n[PASS] test_noOwnerOrPause() (gas: 48489)\n[PASS] test_nobodyCanBurnAnotherHoldersTokens() (gas: 90371)\n[PASS] test_rejectsEther() (gas: 38426)\n[PASS] test_runtimeFitsEip170() (gas: 5013)\n[PASS] test_supplyCannotGrow() (gas: 284488)\n[PASS] test_transferFromWithAllowance() (gas: 183145)\n[PASS] test_transferFromWithoutAllowanceReverts() (gas: 93087)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 37770)\n[PASS] test_transferMovesExactlyWhatIsSent() (gas: 161377)\n[PASS] test_transferToZeroAddressReverts() (gas: 32943)\n[PASS] test_wholeSupplyMintedOnceToDeployer() (gas: 22578)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 4.70ms (6.96ms CPU time)\n\nRan 50 tests for test/IMDPumpRevenueSplitter.t.sol:IMDPumpRevenueSplitterTest\n[PASS] testFuzz_distributionConservesRevenue(uint256) (runs: 256, μ: 550800, ~: 555623)\n[PASS] testFuzz_distributionConservesRevenueUnderAnyAllocation(uint256,uint16,uint16,uint16,uint16) (runs: 256, μ: 595177, ~: 608770)\n[PASS] test_burnPendingRevertsUntilSinkConfigured() (gas: 267578)\n[PASS] test_burnPendingRevertsWithNothingPending() (gas: 101892)\n[PASS] test_burnPendingSendsAccumulatedShareOnceConfigured() (gas: 541562)\n[PASS] test_constructorDoesNotTouchTheLaunchToken() (gas: 2103663)\n[PASS] test_constructorMakesNoExternalCall() (gas: 1479369)\n[PASS] test_constructorRejectsZeroOwner() (gas: 195604)\n[PASS] test_constructorRejectsZeroPayout() (gas: 243467)\n[PASS] test_constructorRejectsZeroRevenueToken() (gas: 243494)\n[PASS] test_constructorState() (gas: 66018)\n[PASS] test_depositAndDistributeIncludesRevenueAlreadyWaiting() (gas: 341926)\n[PASS] test_depositAndDistributePullsFromCaller() (gas: 327360)\n[PASS] test_depositAndDistributeRejectsZero() (gas: 34831)\n[PASS] test_depositAndDistributeWithoutApprovalReverts() (gas: 120850)\n[PASS] test_distributeAppliesTheLaunchModel() (gas: 596581)\n[PASS] test_distributeBurnsImmediatelyWhenSinkConfigured() (gas: 351392)\n[PASS] test_distributeFlushesEarlierDeferredBurn() (gas: 468381)\n[PASS] test_distributeTwiceWithoutNewRevenueReverts() (gas: 270119)\n[PASS] test_distributeWithNothingWaitingReverts() (gas: 42269)\n[PASS] test_distributeWithSinglePayoutAddress() (gas: 245291)\n[PASS] test_everyRecipientSetterSettlesWaitingRevenueFirst() (gas: 855170)\n[PASS] test_invalidAllocationIsRefusedBeforeSettling() (gas: 123505)\n[PASS] test_nonStandardRevenueTokenStillDistributes() (gas: 1947476)\n[PASS] test_ownershipTransfersInTwoSteps() (gas: 138857)\n[PASS] test_pendingBurnIsNotRedistributed() (gas: 398320)\n[PASS] test_previewMatchesDistribution() (gas: 255405)\n[PASS] test_recipientSetters() (gas: 285986)\n[PASS] test_recoverRefusesRevenueToken() (gas: 112615)\n[PASS] test_recoverStrayToken() (gas: 733439)\n[PASS] test_recoverStrayTokenWithoutBoolReturn() (gas: 384098)\n[PASS] test_recoverValidation() (gas: 700728)\n[PASS] test_reentrantRevenueTokenIsRefused() (gas: 2361775)\n[PASS] test_rejectsEther() (gas: 38478)\n[PASS] test_renounceOwnershipIsDisabledEvenWithBurnSinkSet() (gas: 83788)\n[PASS] test_renounceOwnershipIsDisabledWhileBurnSinkUnset() (gas: 419995)\n[PASS] test_renounceOwnershipRefusesStrangers() (gas: 13483)\n[PASS] test_roundingDustGoesToTreasury() (gas: 516769)\n[PASS] test_runtimeFitsEip170() (gas: 5058)\n[PASS] test_setAllocation() (gas: 303852)\n[PASS] test_setAllocationOnlyOwner() (gas: 41110)\n[PASS] test_setAllocationRejectsWrongSum() (gas: 73453)\n[PASS] test_setAllocationSettlesWaitingRevenueUnderTheOldSplit() (gas: 733400)\n[PASS] test_setBurnSinkSettlesWaitingRevenueUnderThePreviousSink() (gas: 355655)\n[PASS] test_setBurnSinkWithNoPreviousSinkKeepsTheWaitingBurnSharePending() (gas: 370775)\n[PASS] test_setDeveloperSettlesWaitingRevenueToThePreviousDeveloper() (gas: 683174)\n[PASS] test_setterSettlementIsReentrancyGuarded() (gas: 2382066)\n[PASS] test_settersOnlyOwner() (gas: 171731)\n[PASS] test_settersRejectZeroAndSelf() (gas: 219557)\n[PASS] test_settersWithNothingWaitingDistributeNothing() (gas: 543542)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 20.58ms (53.91ms CPU time)\n\nRan 3 test suites in 21.25ms (26.16ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDPumpRevenueSplitter.acceptOwnership()\",\"IMDPumpRevenueSplitter.burnPending()\",\"IMDPumpRevenueSplitter.depositAndDistribute(uint256)\",\"IMDPumpRevenueSplitter.distribute()\",\"IMDPumpRevenueSplitter.recoverToken(address,address,uint256)\",\"IMDPumpRevenueSplitter.setAllocation(uint16,uint16,uint16,uint16,uint16)\",\"IMDPumpRevenueSplitter.setBurnSink(address)\",\"IMDPumpRevenueSplitter.setDeveloper(address)\",\"IMDPumpRevenueSplitter.setNftWorkers(address)\",\"IMDPumpRevenueSplitter.setOperations(address)\",\"IMDPumpRevenueSplitter.setTreasury(address)\",\"IMDPumpRevenueSplitter.transferOwnership(address)\",\"IMDPumpToken.approve(address,uint256)\",\"IMDPumpToken.burn(uint256)\",\"IMDPumpToken.transfer(address,uint256)\",\"IMDPumpToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":191,\"foundry.toml\":27,\"script/Deploy.s.sol\":49,\"src/IMDPumpRevenueSplitter.sol\":382,\"src/IMDPumpToken.sol\":31,\"test/Deploy.t.sol\":31,\"test/IMDPumpRevenueSplitter.t.sol\":680,\"test/IMDPumpToken.t.sol\":168,\"test/mocks/MockIMD.sol\":70},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":835,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/IMDPumpRevenueSplitter.sol:311: IMDPumpRevenueSplitter._distribute() (src/IMDPumpRevenueSplitter.sol#311-342) uses a dangerous strict equality:","passed":true},{"durationMs":199,"exitCode":0,"name":"aderyn","output":"[high] storage-array-memory-edit at src/IMDPumpRevenueSplitter.sol:198: Storage Array Edited with Memory\n[low] centralization-risk at src/IMDPumpRevenueSplitter.sol:42: Centralization Risk (9 places)\n[low] large-numeric-literal at src/IMDPumpRevenueSplitter.sol:46: Large Numeric Literal (2 places)","passed":true},{"durationMs":1369,"exitCode":0,"name":"proof 8faeeae0c65e","output":"Compiling 38 files with Solc 0.8.26\nSolc 0.8.26 finished in 838.21ms\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-3bafbdc5/Proof_8faeeae0c65e.t.sol:RenounceStrandsBurnTest\n[PASS] test_renounceOwnershipRefusedWhileBurnSinkUnset() (gas: 265340)\n[PASS] test_renouncedSplitterLocksBurnShareForever() (gas: 261508)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 931.40µs (498.98µs CPU time)\n\nRan 1 test suite in 3.44ms (931.40µs CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f1c65a463586ac99900a8e2fba36c2b5bd6b246443c2d3a8359a700982c1b0dd","verifiedTreeHash":"965bbeccc9d4ebe1b8245fc5678719c3b6ef5837","verifierVersion":"0.1.0+a490e3c0"}]}