{"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":"6321056b-a125-48f2-9c7a-81cc8695a1f2","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"a01c467ca0362ba4176c9d3dfa056a886a6e8f31467420c96cfb3c942ab76814","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3f81669282ba454a3d9128bdfeb898690483cd690322facc5b1bb06fc60798f2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"6ddb3448b40bec7c23df2a618ee7ab4593798f1769163b1e263da05eb4a828e9","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"5ee00973d7e53abadb740f5f1860f2e0bcd13bba3c9335cff5917832197d3cbf","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"60293c7c3738a8d5a1afb8aaa6640b5ae22d1eb582437b63ed166d3c52724b48","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"61d4968ffe0afdfc5c1ea11a495540d561c50f5199fbac60e5be9dfd227f1cb3","dependsOn":[],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"b8b25c0f4f6590d018965896349a81ad11921a3c33f18874bf881568ae096316","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"f21a5ac7d491dd15bf0ffe4b7f7b5135ba2b22429013618f3f301b8302d3195b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"[06-Oct-26 11:56 PM] Ito Hermes in reply to Jet (🤹🏻‍♀️,👻):\n> ‎⁨make it half this amout of charrcters⁩\nIMD Ecosystem Index — Build Request\n\nI want to commission an IMD-native Ethereum ecosystem index funded by project-token trading fees.\n\nA Uniswap v4 hook collects fees from the project-token pool. A defined portion goes into a treasury reserve. Once per day, the IMD swarm researches Ethereum ecosystem tokens and proposes a top-five basket. Deterministic on-chain rules decide which assets are eligible.\n\nThe swarm should research, rank, and stress-test assets, but must not have unrestricted trading authority. This should be an auditable index with an AI research layer, not a black-box AI trading bot.\n\nCore loop\n\nProject-token swaps → v4 fee hook → fee waterfall → treasury reserve → daily market snapshot → swarm research → risk filters → signed proposal → delay → protected execution → top-five basket.\n\nV1 scope\n\n- Ethereum ecosystem assets only.\n- Maximum five basket assets.\n- Initial weighting: 20% per asset, subject to caps and liquidity.\n- Ethereum mainnet for the project pool and index identity.\n- Daily research and ranking, but weekly execution initially.\n- Rebalance only when membership or weights cross thresholds.\n- Use rank buffers to prevent constant turnover.\n- Use CoW Swap or another MEV-resistant batch route where possible.\n\nFee waterfall\n\n- 40% to basket reserve.\n- 25% to liquidity providers.\n- 20% to swarm/operator reserve.\n- 10% to protocol reserve and operating costs.\n- 5% to token utility reserve until utility is defined.\n\nThese percentages are starting parameters. They must be visible on-chain and controlled by timelocked configuration. The basket reserve should accumulate in ETH or USDC before execution.\n\nEligibility and ranking\n\nA token must have an approved Ethereum address, be at least 30 days old, meet minimum market cap/volume/liquidity requirements, have a fresh price oracle, and pass contract, holder-concentration, upgrade, and security checks.\n\nExclude tokens with stale data, unverifiable supply, suspicious volume, inadequate liquidity, failed trade simulation, honeypot behavior, or unresolved critical incidents.\n\nInitial ranking:\n\n- 50% circulating market cap\n- 20% liquidity and volume quality\n- 15% 30-day momentum\n- 10% ecosystem usage, fees, or revenue\n- 5% swarm research score\n\nThe swarm score cannot override a hard exclusion. Publish the data sources, snapshot time, supply methodology, formula, caps, buffers, and stale-data rules.\n\nSwarm workflow\n\nThe swarm produces a signed daily report with the ranked tokens, proposed weights, addition/removal reasoning, contract-risk findings, liquidity warnings, unlock and insider analysis, ecosystem activity, confidence, sources, and dissenting opinions.\n\n1. One agent ranks candidates.\n2. Other agents attack the ranking and search for manipulation or risk.\n3. A verifier checks the evidence.\n4. An orchestrator signs the proposal.\n5. The executor validates it against the hard rules.\n\nAgents should be rewarded for verified research and risk detection, not merely bullish calls.\n\nProposal and execution\n\nEach basket state has an epoch. A signed proposal includes the epoch, snapshot time, token addresses, target weights, methodology version, data hash, expiry, and signer/quorum information.\n\nReject expired, stale, replayed, malformed, over-weighted, or non-allowlisted proposals. Reject stale price feeds and trades that fail minimum-output limits. Use a short delay between publication and execution.\n\nUse a separate vault and rebalance executor. Do not perform the full portfolio rebalance inside the swap hook. The executor should trade only the delta between old and new targets, preserve an ETH/USDC buffer, enforce slippage limits, and quarantine failed assets rather than bricking the basket.\n\nThe vault should provide deposits, redemptions, ERC-20 index shares, transparent NAV, published holdings, emergency pause, and safe redemption if the swarm or keeper is offline.\n\nUniswap v4 hook scope\n\nThe primary hook should:\n[06-Oct-26 11:56 PM] Ito Hermes in reply to Jet (🤹🏻‍♀️,👻):\n> ‎⁨make it half this amout of charrcters⁩\n- collect the project-token swap fee;\n- split it according to the fee waterfall;\n- record the basket-reserve amount;\n- emit indexed fee events;\n- avoid trading the basket inline.\n\nA secondary epoch hook may store the active basket/proposal hash, reject stale updates, enforce approved tokens and executors, prevent replay, and apply bounded dynamic fees when the basket is stale or volatile.\n\nSecurity and delivery\n\nSeparate the FeeHook, ProposalSigner/quorum, Executor, Guardian, and TimelockedAdmin roles. No single wallet should control every function.\n\nBefore real funds, require Foundry tests, mainnet-fork tests, fuzzing, solvency and fee-accounting invariants, v4 callback/reentrancy tests, stale-oracle and failed-execution tests, MEV/slippage simulations, an independent audit, and a capped beta.\n\nProposal requested\n\nPlease provide a milestone-based proposal covering the fee hook, fee waterfall, basket methodology, swarm workflow, signed proposals, vault, executor, protected execution, frontend, testing, audit scope, gas assumptions, timeline, ownership/IP, repository handoff, named lead agent, human technical owner, and post-launch support.\n\nFinal product: Fee-to-Basket Hook + Swarm Research + Rules-Based Top-Five Vault. Do not build an unrestricted AI trading bot.\n\nToken name: IMD Index\nToken symbol: IMDEX","parentJobId":null,"planHash":"ca3b4a2f2cd39358cea6edb58e7a91cc15befd53e23f42a3f8e64657dc6ca525","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"6321056b-a125-48f2-9c7a-81cc8695a1f2","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-816-imd-index"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52121","feedbackHash":"ae126cb0bd6e6c35ccabf0606ddc977e58d71ca016b1eb28b70d8df111906b6e","nodeKey":"audit_economics","submissionHash":"a01c467ca0362ba4176c9d3dfa056a886a6e8f31467420c96cfb3c942ab76814","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52120","feedbackHash":"b2747de326cc5f0a7d73df263dd5b827b047a02b4681fbb319126e3bd00e4f43","nodeKey":"audit_flow","submissionHash":"3f81669282ba454a3d9128bdfeb898690483cd690322facc5b1bb06fc60798f2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52167","feedbackHash":"b5e9e236765459da9e1e7d792c27e9817a31ed504ed694b440d9813292189817","nodeKey":"audit_judge","submissionHash":"6ddb3448b40bec7c23df2a618ee7ab4593798f1769163b1e263da05eb4a828e9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51143","feedbackHash":"93dd2c11c330f6a015968b2e56decca6a41b0c26c0da9f914c25454a6d47f5d5","nodeKey":"audit_judge","submissionHash":"be24de4edcb027ea9b4b15b1cf69cfb3175e63c79c8476eee01f93b9a6d07acb","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51538","feedbackHash":"7e8cd70fdb8e748ae6bd14a77ebf234eb9732dfb8cb03f565ab63d227ed7f330","nodeKey":"audit_math","submissionHash":"5ee00973d7e53abadb740f5f1860f2e0bcd13bba3c9335cff5917832197d3cbf","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52128","feedbackHash":"199fe52ab1ee945fca14165bc09cd20477a3daa91f201311e3f0fafb895f6484","nodeKey":"audit_permissions","submissionHash":"60293c7c3738a8d5a1afb8aaa6640b5ae22d1eb582437b63ed166d3c52724b48","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51727","feedbackHash":"1475eae12fe46f2b0f6741ce9c2cdd7594a0268ca19ef271676ec50efb3dc226","nodeKey":"build_contract_project","submissionHash":"61d4968ffe0afdfc5c1ea11a495540d561c50f5199fbac60e5be9dfd227f1cb3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52271","feedbackHash":"836580f22c15d5fe24df40725fc15816c4bd9436ffb6ae3c61f0ee7f4748aac9","nodeKey":"build_contract_project","submissionHash":"b9b7d181fa84d3465d1b63b472d342781c50da242fd41704fa56f14368f94e87","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51318","feedbackHash":"0e3ad2f6c6b7cc933a6d4b1f64da01289175e387a915602a1c7171c759024a82","nodeKey":"manifest","submissionHash":"b8b25c0f4f6590d018965896349a81ad11921a3c33f18874bf881568ae096316","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51523","feedbackHash":"f3b022afeb8b36f36d0dd1e1fe99bae32279d01703fb2d372d344439d401a4aa","nodeKey":"manifest","submissionHash":"ee4bd8bbc805b0fd10bd620c15f7a690a35452944ef02082021a66ccf9fddfd5","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51868","feedbackHash":"3bf9f71a62a2c876da122dc89bc66c0d24891d212076a454f2605a3d43117feb","nodeKey":"write_foundry_tests","submissionHash":"f21a5ac7d491dd15bf0ffe4b7f7b5135ba2b22429013618f3f301b8302d3195b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51078","feedbackHash":"e616ad48497d253a2453852bae1426138abc7ccf85add5a172b0e212bf765e49","nodeKey":"write_foundry_tests","submissionHash":"940f9713e241c84880794329bd0353c000a49bc55fd90d6d926b5f16b328924d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"dd9a2fcd96efb95da295b53405e4c821176254d0aa25778e71ef80edc08eadea","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a5c5e95a2ed07117","findings":[{"citation":"resolved","description":"Every configuration change, including `setGuardian` (src/TimelockedAdmin.sol:179, `onlySelf`), is reachable only through `schedule` -> wait `minDelay` (>= 1 day) -> `execute`. `cancel` lets the guardian delete any scheduled id, with no restriction on the target or selector, and the `Scheduled` event publishes the id at least one day before it can run. A compromised or hostile guardian therefore cancels every `setGuardian`, `unpause`, `setSigner`, `setKeeper` and `setExecutor` operation the moment it is scheduled, while using its immediate powers (`pause`, `revokeSigner`, `revokeKeeper`, `cancelPending`) to stop deposits, proposals and trading. There is no other path to change the guardian or lift the pause: `pause`/`unpause` are guardian-or-timelock only and `transferAdmin`/`setGuardian` are timelock only. docs/SECURITY.md states the guardian is limited because 'the timelock can replace the guardian and lift a pause'; that limit does not exist. The result is a permanent freeze of everything except in-kind redemption (fee basket reserve stuck in FeeWaterfall because `pushBasketReserve` needs an unpaused vault, no new epochs, no rebalancing), with no recovery by any party. Suggested minimal fix that keeps the requested guardian role: make operations whose target is the timelock itself and whose selector is `setGuardian` (or, more simply, any self-targeted operation) cancellable only by the admin wallet; or let `execute` ignore a guardian cancel for `setGuardian`.","line":146,"path":"src/TimelockedAdmin.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\n\n/// @dev A hostile or compromised guardian can veto its own replacement forever: `setGuardian` is\n/// reachable only through `execute`, and `cancel` is open to the guardian for every scheduled id.\n/// The documented limit on the guardian (\"the timelock can replace the guardian and lift a pause\")\n/// therefore does not hold. This test fails on the current code and passes once a replacement of\n/// the guardian can no longer be vetoed by the guardian itself.\ncontract GuardianIrremovableTest is Test {\n    address internal constant ADMIN = address(0xA11CE);\n    address internal constant ROGUE = address(0x6A4D);\n    address internal constant HONEST = address(0x6A4E);\n    uint256 internal constant DELAY = 1 days;\n\n    TimelockedAdmin internal tl;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        tl = new TimelockedAdmin(ADMIN, DELAY);\n        _govSetGuardian(ROGUE, bytes32(uint256(1)), address(0));\n        assertEq(tl.guardian(), ROGUE);\n    }\n\n    function test_adminCanReplaceAGuardianThatVetoesItsOwnReplacement() public {\n        // The guardian also pauses the system: once it is irremovable, the pause is permanent too.\n        vm.prank(ROGUE);\n        tl.pause();\n\n        _govSetGuardian(HONEST, bytes32(uint256(2)), ROGUE);\n\n        assertEq(tl.guardian(), HONEST, \"the timelock must be able to replace the guardian\");\n    }\n\n    /// @dev Schedules setGuardian(next); `vetoer` (if any) tries to cancel it; then the admin executes.\n    function _govSetGuardian(address next, bytes32 salt, address vetoer) internal {\n        bytes memory data = abi.encodeCall(tl.setGuardian, (next));\n        vm.prank(ADMIN);\n        bytes32 id = tl.schedule(address(tl), 0, data, salt, DELAY);\n\n        if (vetoer != address(0)) {\n            vm.prank(vetoer);\n            // A fix may either refuse this cancel or let the execution proceed regardless.\n            try tl.cancel(id) {} catch {}\n        }\n\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n    }\n}","reproduction":"State: TimelockedAdmin deployed with admin A and minDelay 1 day; guardian G set through the timelock. Sequence: (1) G calls `pause()`. (2) A calls `schedule(address(tl), 0, abi.encodeCall(setGuardian, (H)), salt, 1 days)` -> emits Scheduled(id). (3) G calls `cancel(id)` -> succeeds (readyAt deleted). (4) After 1 day A calls `execute(address(tl), 0, data, salt)` -> reverts NotScheduled. Expected (per docs/SECURITY.md trust model): the timelock can replace the guardian; actual: guardian stays G forever, pause stays, every later schedule can be cancelled the same way. Proof test fails with NotScheduled() on the current code.","severity":"medium","snippet":"    function cancel(bytes32 id) external {\n        if (msg.sender != admin && msg.sender != guardian) revert NotAuthorized();","title":"Guardian can veto its own replacement: setGuardian is a timelocked op the guardian may cancel, so a hostile guardian is irremovable and can freeze governance permanently"},{"citation":"resolved","description":"`_checkBasket` only enforces `MaxAdditionsPerEpoch` when the active basket has at least one token (line 332, used at line 346 `if (hadBasket && !_isActiveMember(token)) ++additions;`). Nothing requires a proposal to contain any token: `n > MAX_ASSETS` is the only length rule and the per-token `weight == 0` check is vacuous for n == 0, so a proposal with empty arrays and a non-zero `dataHash` publishes and activates. Once the active basket is empty, the next proposal is treated like the first basket and may introduce five new members at once. The documented hard rule ('at most MaxAdditionsPerEpoch (2) new members per epoch', README and docs/METHODOLOGY.md section 4) is therefore enforceable only while signers choose to keep a non-empty basket. Signers are trusted, but the turnover limit exists precisely to bound what a quorum can do, and docs/SECURITY.md lists 'turnover limit' as the limit on a malicious quorum. Side effect: with an empty active basket every held position has target 0, so the keeper may liquidate the entire basket through the exit path, which needs no window and no drift threshold. Minimal fix: require `n != 0` in `_checkBasket` (an index with no members is not a valid state), and/or count additions against the last non-empty basket.","line":332,"path":"src/EpochManager.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 {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {EpochManager} from \"src/EpochManager.sol\";\n\ncontract ScratchToken is ERC20 {\n    constructor() ERC20(\"T\", \"T\") {}\n}\n\ncontract ScratchFeed {\n    uint8 public constant decimals = 8;\n\n    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (1, 1e8, block.timestamp, block.timestamp, 1);\n    }\n}\n\n/// @dev `_checkBasket` only counts additions when the active basket is non-empty, and a proposal\n/// with zero tokens passes every rule. A quorum can therefore activate an empty basket and, one\n/// epoch later, five brand-new members, although `MaxAdditionsPerEpoch` is 2.\ncontract EmptyBasketTurnoverTest is Test {\n    address internal constant ADMIN = address(0xA11CE);\n    uint256 internal constant DELAY = 1 days;\n    uint256[2] internal keys = [uint256(0x51), uint256(0x52)];\n\n    TimelockedAdmin internal tl;\n    AssetRegistry internal registry;\n    EpochManager internal epochs;\n    ScratchToken internal reserve;\n    ScratchFeed internal feed;\n    address[] internal toks;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        reserve = new ScratchToken();\n        feed = new ScratchFeed();\n        tl = new TimelockedAdmin(ADMIN, DELAY);\n        registry = new AssetRegistry(address(tl), address(reserve), 18);\n        epochs = new EpochManager(address(tl), address(registry));\n\n        bytes32 salt = bytes32(uint256(1));\n        bytes[] memory calls = new bytes[](3 + 10);\n        address[] memory targets = new address[](3 + 10);\n        targets[0] = address(tl);\n        calls[0] = abi.encodeCall(tl.setSigner, (vm.addr(keys[0]), true));\n        targets[1] = address(tl);\n        calls[1] = abi.encodeCall(tl.setSigner, (vm.addr(keys[1]), true));\n        targets[2] = address(tl);\n        calls[2] = abi.encodeCall(tl.setQuorum, (2));\n        for (uint256 i; i < 10; ++i) {\n            address t = address(new ScratchToken());\n            toks.push(t);\n            targets[3 + i] = address(registry);\n            calls[3 + i] = abi.encodeCall(\n                registry.approveToken, (t, address(feed), 1 days, 2000, uint40(block.timestamp - 31 days), bytes32(\"r\"))\n            );\n        }\n        vm.startPrank(ADMIN);\n        for (uint256 i; i < calls.length; ++i) {\n            tl.schedule(targets[i], 0, calls[i], salt, DELAY);\n        }\n        skip(DELAY);\n        for (uint256 i; i < calls.length; ++i) {\n            tl.execute(targets[i], 0, calls[i], salt);\n        }\n        vm.stopPrank();\n    }\n\n    function test_emptyBasketResetsTheTurnoverLimit() public {\n        // Epoch 1: five members.\n        _activate(_slice(0, 5));\n        assertEq(epochs.activeBasket().tokens.length, 5);\n\n        // Epoch 2: an empty basket passes every hard rule.\n        skip(7 days);\n        _activate(new address[](0));\n        assertEq(epochs.activeBasket().tokens.length, 0);\n        assertEq(epochs.epoch(), 2);\n\n        // Epoch 3: five brand-new members, although at most two additions per epoch are allowed.\n        skip(7 days);\n        EpochManager.Proposal memory p = _proposal(_slice(5, 5));\n        vm.expectRevert(EpochManager.TooManyAdditions.selector);\n        epochs.publish(p, _sign(p));\n    }\n\n    function _slice(uint256 from, uint256 n) internal view returns (address[] memory out) {\n        out = new address[](n);\n        for (uint256 i; i < n; ++i) {\n            out[i] = toks[from + i];\n        }\n    }\n\n    function _proposal(address[] memory members) internal view returns (EpochManager.Proposal memory p) {\n        uint256 n = members.length;\n        p.epoch = epochs.epoch() + 1;\n        p.snapshotTime = uint64(block.timestamp - 1 hours);\n        p.expiry = uint64(block.timestamp + 2 days);\n        p.methodologyVersion = registry.methodologyVersion();\n        p.signerSetVersion = tl.signerSetVersion();\n        p.dataHash = keccak256(abi.encode(\"report\", block.timestamp));\n        p.tokens = members;\n        p.weightsBps = new uint16[](n);\n        p.marketCapsUsd = new uint256[](n);\n        p.liquidityUsd = new uint256[](n);\n        p.volumesUsd = new uint256[](n);\n        for (uint256 i; i < n; ++i) {\n            p.weightsBps[i] = 2000;\n            p.marketCapsUsd[i] = 1_000_000_000;\n            p.liquidityUsd[i] = 50_000_000;\n            p.volumesUsd[i] = 50_000_000;\n        }\n    }\n\n    function _sign(EpochManager.Proposal memory p) internal view returns (bytes[] memory sigs) {\n        bytes32 digest = epochs.hashProposal(p);\n        uint256 a = keys[0];\n        uint256 b = keys[1];\n        if (vm.addr(b) < vm.addr(a)) (a, b) = (b, a);\n        sigs = new bytes[](2);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(a, digest);\n        sigs[0] = abi.encodePacked(r, s, v);\n        (v, r, s) = vm.sign(b, digest);\n        sigs[1] = abi.encodePacked(r, s, v);\n    }\n\n    function _activate(address[] memory members) internal {\n        EpochManager.Proposal memory p = _proposal(members);\n        epochs.publish(p, _sign(p));\n        skip(6 hours);\n        epochs.activate();\n    }\n}","reproduction":"State: active basket epoch 1 with tokens T0..T4, MaxAdditionsPerEpoch = 2, 10 approved tokens, quorum 2. Sequence: (1) after 7 days publish a proposal with epoch 2, tokens = [], weightsBps = [], marketCapsUsd/liquidityUsd/volumesUsd = [], dataHash != 0, fresh snapshot, signed by quorum -> accepted; activate after the delay -> active basket has 0 tokens. (2) after 7 more days publish epoch 3 with tokens = [T5..T9] (five tokens not in epoch 1) -> expected revert TooManyAdditions, actual: accepted. The attached test fails on current code ('next call did not revert as expected') and passes once empty baskets are refused or additions are counted against the last non-empty basket.","severity":"low","snippet":"        bool hadBasket = _active.tokens.length != 0;","title":"MaxAdditionsPerEpoch is bypassed by an empty basket: a zero-token proposal passes every rule and resets the turnover counter"},{"citation":"resolved","description":"README ('only inside the rebalance window (2 days from a new epoch or once per interval)') and docs/METHODOLOGY.md ('RebalanceWindow ... that opens with a new epoch') describe a window anchored to activation. `_requireWindow` instead anchors it to the first non-exit trade after the epoch changes: while `windowEpoch != epochs.epoch()` the check always passes and sets `windowStart = block.timestamp`. A keeper that has not traded since activation can therefore pick any moment, days or weeks later, to open a fresh 2-day window, and the 'once per RebalanceInterval' cadence restarts from that moment. The slippage floor and delta rules still bound each trade, so this is a schedule/guarantee defect rather than a fund-loss path, but it removes the timing constraint the documentation promises and lets the keeper choose the trading moment (e.g. a period of thin liquidity for a colluding counterparty, still within MaxSlippageBps). Minimal fix: read `activatedAt` from the active basket (expose it via IEpochManager) and set `windowStart = activatedAt` when the epoch changes, applying the same closed/reopen arithmetic from there.","line":271,"path":"src/RebalanceExecutor.sol","reproduction":"State: epoch 1 activated at time A (one member, 20% weight), RebalanceWindow = 2 days, RebalanceInterval = 7 days, vault holds 1,000,000 USDC, no trade yet. At A + 20 days the keeper calls `executeTrade(USDC, tok, deficit, 0, router, swapData)`. Under the documented schedule the windows are [A, A+2d], [A+7d, A+9d], [A+14d, A+16d], [A+21d, A+23d], so A+20d should revert WindowClosed. Actual: the trade succeeds and `windowStart()` becomes A + 20 days (test test/scratch/WindowOpensOnFirstTrade.t.sol, passes on current code, demonstrating the behaviour).","severity":"low","snippet":"        if (current != windowEpoch) {\n            windowEpoch = current;\n            windowStart = uint64(block.timestamp);","title":"Rebalance window opens at the keeper's first trade in an epoch, not at activation, so the keeper may trade at any time it has not yet traded in the epoch"},{"citation":"resolved","description":"docs/SECURITY.md already states that a keeper can force a quarantine by submitting failing swaps, so the keeper side is a documented trust assumption. The part not documented is the unprivileged amplifier: a failure is also recorded when an honest keeper's swap under-delivers because a third party moved the venue price in the same block (`InsufficientOutput` is thrown inside `swapThroughRouter`, so it lands in the catch). Three such failures in a row (no success in between, since only success deletes the counter) quarantine the member; afterwards `_target` is zero for it, the keeper may sell the whole position without window or drift threshold (`exit` branch of `_authorize`), it cannot be bought back, and only a timelocked `releaseQuarantine` (>= 1 day) restores it. The README mitigation is a private relay, which is operational rather than enforced. Reported for the judge's awareness; no code defect beyond the documented one.","line":124,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket with T0, keeper submits `executeTrade(USDC, T0, 100_000e6, 0, router, data)` three times through the public mempool. Each time a searcher front-runs so the fill is 1.01% below oracle; each call returns (false, 0) and increments `failureCount[T0]`; after the third, `registry.isQuarantined(T0)` is true and `position(T0).target == 0`. Observed with the existing suite's mechanism in test_fillBelowTheOracleFloorIsUndoneAndCounted and test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket (a failing fill counts exactly like a reverting route).","severity":"info","snippet":"            uint256 failures = ++failureCount[token];\n            if (failures >= registry.param(Param.FailureThreshold) && !registry.isQuarantined(token)) {\n                registry.quarantine(token);","title":"Auto-quarantine counts failures caused by third parties: three sandwiched (under-delivering) keeper trades quarantine a basket member and open the unrestricted exit path for it"}],"hash":"3f81669282ba454a3d9128bdfeb898690483cd690322facc5b1bb06fc60798f2","nodeId":"83d265e0-a037-463e-9b6a-63da8a2591d3","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds 4 findings and a 60-row coverage record (all 57 listed entry points plus 3 invariants). The working tree is otherwise unchanged; the scratch tests were removed after running, with the two proof sources embedded in the JSON.\n\n**Findings**\n\n- **Medium, `src/TimelockedAdmin.sol:146`.** The guardian can cancel any scheduled operation, and `setGuardian`, `unpause`, `setSigner`, `setKeeper` and `setExecutor` exist only as scheduled operations. A hostile or compromised guardian therefore pauses the system, revokes signers and keepers, and vetoes every repair, including its own replacement, forever. The docs claim \"the timelock can replace the guardian and lift a pause\"; it cannot. Only in-kind redemption survives. Proof test attached: it fails with `NotScheduled()` today.\n- **Low, `src/EpochManager.sol:332`.** A proposal with zero tokens passes every hard rule, and additions are only counted when the active basket is non-empty. A quorum can activate an empty basket, then five brand-new members one epoch later, bypassing `MaxAdditionsPerEpoch`. An empty basket also makes every held position an exit, sellable with no window or drift threshold. Proof test attached.\n- **Low, `src/RebalanceExecutor.sol:271`.** The rebalance window is anchored to the keeper's first trade of an epoch, not to activation as the README and methodology state. A keeper that has not traded yet may open a fresh two-day window at any moment, confirmed by a trade 20 days after activation.\n- **Info, `src/RebalanceExecutor.sol:124`.** The keeper-forced quarantine is documented, but the failure counter also rises when a third party sandwiches an honest keeper's trade below the floor. Three such fills quarantine the member and open its unrestricted exit path.\n\n**Coverage.** All entry points in the FeeHook, FeeWaterfall, IndexVault, AssetRegistry, LaunchToken and FeeHookDeployer were traced and hold. The hook's `BeforeSwapDelta` and `afterSwap` return-delta signs were checked against the vendored v4-core `Hooks` library for all four swap shapes. The `_swap` try/catch rollback, vault lock, approval reset and output measurement hold. The slither reentrancy and strict-equality leads were traced and are not reachable defects. Not run: Slither, fork tests, long fuzz campaigns.","treeHash":null,"usage":{"cachedInputTokens":2357172,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":69623,"runtime":"claude","turns":41,"wallClockMs":992286}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"866bf60e68b081d9","findings":[{"citation":"resolved","description":"IndexVault._nav() values every held token at oracle price as long as the feed is fresh. It never consults AssetRegistry.isQuarantined, so a position the system itself has classified as failed (honeypot, blocked transfers, failed trade simulation) keeps its full oracle value in the NAV that prices deposits. Redemptions are in kind, and a token that refuses transfers from the vault is skipped with strict=false (the slice stays in the vault) or reverts with strict=true. So a depositor who enters after the quarantine is charged for the quarantined token's share of NAV but can never take it out; that value is transferred to the earlier holders. Seam: boundary (external token refuses transfers) x invariant (shares priced at NAV == realizable value). The README's doctrine is 'a deposit that cannot be priced fairly is refused rather than guessed', which is applied to the unpriced case (line 125) but not to the quarantined case. Unprivileged amplifier: anyone can call FeeWaterfall.pushBasketReserve(0), which deposits the treasury's basket reserve at this inflated NAV; an existing holder gains from each push. The executor cannot clear the position either: beginTrade's safeTransfer reverts for a transfer-blocked token, so disposeQuarantined fails too and the mispricing is permanent. Fix (minimal, preserves design): in deposit(), also revert when any held token with non-zero balance is quarantined (treat it like unpriced), or at least value quarantined holdings at zero for deposit pricing and document the choice.","line":318,"path":"src/IndexVault.sol","reproduction":"State: Alice deposited 1,000,000 USDC; basket of one token TOK (weight 2000, price $100) activated; keeper bought TOK to its 196,000 USDC target so the vault holds ~1,960 TOK. TOK then blocks transfers from the vault; the guardian calls registry.quarantine(TOK); TOK's feed stays fresh. vault.nav() still returns ~1,000,000e6 with complete == true. Bob calls vault.deposit(100_000e6, BOB, 0): expected either a revert (held token quarantined) or shares that are worth 100,000 USDC in a round trip; actual: Bob receives 100,000e12 shares priced against the full NAV. Bob calls vault.redeem(shares, BOB, false) immediately: he receives 82,281.8 USDC of value (his reserve slice plus nothing from TOK, whose transfer fails and is skipped). Loss 17,718 USDC (17.7%) in one block, credited to Alice. In the five-token fixture (T1 honeypot + quarantine, Bob deposits 100,000 USDC) the measured loss is 17,818 USDC. With strict=true the redemption reverts with TransferFailed(TOK) instead, so Bob is locked in until he accepts the loss.","severity":"medium","snippet":"            if (ok) (tokenValue, ok) = registry.convert(token, balance, asset);","title":"Deposit NAV counts a quarantined (untransferable) held token at full oracle value; new depositors pay for value they cannot receive"},{"citation":"resolved","description":"redeem() documents strict as 'any token whose transfer fails reverts the redemption'. But the readable flag returned by _balanceOf is discarded: an unreadable token reads as balance 0, amount becomes 0, the loop hits `continue` on line 163 and no transfer is attempted, so the strict branch on line 167 is never reached. The shares are burned and the redeemer's slice of that token stays in the vault, exactly the outcome strict mode exists to refuse. Boundary: external balanceOf reverts or returns short data (or exceeds the 500k gas cap). Fix: when strict is true and readable is false, revert TransferFailed(token) (or a dedicated error) before computing amount.","line":161,"path":"src/IndexVault.sol","reproduction":"Fixture: Alice deposits 1,000,000 USDC, the top-five basket is bought, Alice is the only holder. tokens[3].setBalanceReverts(true) (balanceOf reverts). Alice calls vault.redeem(allShares, ALICE, true). Expected: revert TransferFailed(tokens[3]) so Alice can wait or choose strict=false knowingly. Actual: the call succeeds, totalSupply becomes 0, Alice receives 0 of tokens[3], and 195,999,999,921 units (~196,000 USDC of value at the last price) of tokens[3] remain in the vault with no shares outstanding against them.","severity":"low","snippet":"            (uint256 balance,) = _balanceOf(token);","title":"redeem(strict=true) silently forfeits the slice of a held token whose balanceOf is unreadable"},{"citation":"resolved","description":"When the quote currency is the specified amount, the fee is computed in beforeSwap from params.amountSpecified and taken immediately. If the pool stops at sqrtPriceLimitX96 (a partial fill), the pool consumes only part of (specified - fee) and returns the rest to the swapper, but the hook fee on the whole requested amount has already gone to the waterfall. The afterSwap path (quote unspecified) charges on the actual delta, so the two paths implement different fee bases. Seam: boundary (price limit reached) x precision (fee base). The effective fee rate on a limited swap is unbounded relative to executed volume; a sandwich that moves the price to the victim's limit before the swap makes the victim pay the full fee on near-zero execution. Fix: when the quote is specified, charge in afterSwap on the executed quote delta (the hook already returns afterSwapReturnDelta), or compute the fee in afterSwap from (amountSpecified - amountSpecifiedRemaining).","line":138,"path":"src/FeeHook.sol","reproduction":"Real PoolManager, USDC quote, liquidity 1e24 around price 1:1. Trader swaps exact-input 100,000 USDC for IMDEX with sqrtPriceLimitX96 = currentSqrtPrice * (1 - 1/10000). Expected: fee = 0.75% of the quote actually swapped (~0.75 USDC on ~100 USDC executed). Actual: hook fee = 750 USDC (100,000 * 7500 / 1e6) taken from the trader, pool consumed 100.25 USDC, trader received 99.99 IMDEX; fee is 748% of the executed quote amount. Measured: quote paid total 850.25e18, hook fee 750e18, imdex received 99.99e18.","severity":"low","snippet":"            uint256 specified =\n                params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"beforeSwap charges the hook fee on the requested quote amount, not on what the swap executes, so a partial fill pays fee on unfilled volume"},{"citation":"resolved","description":"The reserve asset's decimals are supplied twice in the launch manifest: to AssetRegistry (contract #2) and to IndexVault (contract #4). The README says 'the vault refuses its first deposit if reserveDecimals does not match the token', but that check (IndexVault.sol:120) only validates the vault's own copy. The registry's copy feeds _decimalsOf() and therefore every convert() result: NAV, targets, the executor's ExceedsDelta bound and the oracle floor oracleMinOut. Nothing compares it to the token or to the vault. Decimal mismatch (Math Precision guide). Fix: have IndexVault's first-deposit check also require registry.reserveDecimals() == decimals(), or have AssetRegistry read IERC20Metadata(reserveAsset).decimals() lazily on first use.","line":84,"path":"src/AssetRegistry.sol","reproduction":"Deploy with AssetRegistry(admin, USDC, 18) and IndexVault(admin, registry, USDC, 6, cap) (one wrong literal in the manifest). Alice's first deposit of 1,000,000 USDC succeeds (vault copy is right). Basket activated; executor.position(T0) reports target 196,000e6. registry.convert(USDC, 196_000e6, T0) returns 98,000,000 wei of T0 instead of 98e18 (1e12 too small), so the keeper's oracle floor is 1e12 times too low. Keeper trade of 196,000 USDC for T0 filled at 98,000,000 wei succeeds (ok == true); the vault paid 196,000 USDC for 0.000000000098 T0. Expected: the mismatch is refused at deployment/first use, as the README claims.","severity":"low","snippet":"        reserveDecimals = reserveDecimals_;","title":"AssetRegistry's reserveDecimals is never verified against the token or the vault; a mismatch mis-scales every price conversion by 10^k and the keeper's oracle floor with it"},{"citation":"resolved","description":"EpochManager._checkTiming requires p.expiry > readyAt (= now + ProposalDelay) and p.expiry <= now + MAX_VALIDITY (7 days). With ProposalDelay == 7 days both cannot hold: expiry <= now + 7d reverts ExpiryTooSoon and expiry > now + 7d reverts ExpiryTooFar. paramBounds admits exactly that value, so the timelock can put the system into a state where no basket can ever be published until another timelocked change reverts it. Boundary: the closed interval at the parameter cap. Fix: bound ProposalDelay strictly below MAX_VALIDITY (e.g. hi = 7 days - 1 hours), or make _checkTiming's ExpiryTooFar test relative to readyAt.","line":221,"path":"src/AssetRegistry.sol","reproduction":"Fixture; registry.setParam(Param.ProposalDelay, 7 days) through the timelock succeeds. Build a valid signed top-five proposal and try publish with expiry = now + 7 days: reverts ExpiryTooSoon (0x7c5a534c); expiry = now + 7 days + 1: reverts ExpiryTooFar (0x4828eeca); expiry = now + 6 days or now + 1: ExpiryTooSoon. No expiry value is accepted. Expected: the bound should exclude values that leave no valid expiry.","severity":"low","snippet":"        if (key == Param.ProposalDelay) return (1 hours, 7 days);","title":"ProposalDelay upper bound equals MAX_VALIDITY, so the allowed value 7 days makes every proposal unpublishable"}],"hash":"5ee00973d7e53abadb740f5f1860f2e0bcd13bba3c9335cff5917832197d3cbf","nodeId":"67484f3f-ea37-46e3-9964-7b30d70bddee","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` with a coverage row for every one of the 57 listed entry points; no tracked file was changed (git status is clean apart from the findings file and the scratch proof, which are not kept).\n\n**What I found (5 findings, 1 medium, 4 low)**\n\n1. **Medium, IndexVault deposit pricing.** The vault's NAV counts a held token at full oracle value even after the registry has quarantined it, and the vault cannot tell that the token refuses transfers. A depositor who enters after such a quarantine pays for a slice they can never withdraw. Measured: Bob deposits 100,000 USDC and an immediate round trip returns 82,282 USDC, with the difference credited to earlier holders. Anyone can amplify it by calling `pushBasketReserve(0)` to make the treasury buy in at the inflated NAV. The self-contained Foundry proof at `test/scratch/QuarantinedHoldingDeposit.t.sol` fails on the current code and passes if deposits are refused or quarantined holdings are valued at zero.\n2. **Low, strict redemption.** With `strict=true`, a held token whose `balanceOf` is unreadable is silently skipped rather than reverting, so the redeemer burns shares and forfeits that slice. Measured: Alice's full redemption left ~196,000 USDC of value in the vault with zero shares outstanding.\n3. **Low, FeeHook beforeSwap.** When the quote currency is the specified amount, the fee is charged on the requested amount, so a swap that stops at its price limit pays fee on unfilled volume. Measured in the real PoolManager: 750 USDC fee on ~100 USDC executed.\n4. **Low, AssetRegistry reserveDecimals.** The registry's copy of the reserve decimals is never verified against the token or the vault, unlike the vault's copy. With registry=18 and vault=6 the oracle floor is 1e12 times too low and a keeper fill of 196,000 USDC for 0.000000000098 T0 is accepted.\n5. **Low, ProposalDelay bound.** `paramBounds` allows a 7-day delay, which equals the 7-day validity cap, so every expiry is rejected as either too soon or too far and no proposal can be published until the parameter is changed again.\n\n**Coverage.** I traced the arithmetic in the vault share formula and virtual offset, the waterfall split and its relation to the hook's per-swap record, the hook fee on all four swap shapes, the executor's delta, buffer, drift-threshold and oracle-floor rounding, the registry's price scaling and conversion, and the epoch timing boundaries. The remaining entry points hold on the paths in my area. Not reached: live non-standard tokens, mainnet feeds, and gas-bomb behaviour of a malicious held token inside the executor's uncapped `balanceOf` call, which I left as a lead rather than a finding.","treeHash":null,"usage":{"cachedInputTokens":2943931,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":72421,"runtime":"claude","turns":50,"wallClockMs":1075485}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9c6767b941fcfedc","findings":[{"citation":"resolved","description":"TimelockedAdmin.cancel lets the guardian cancel any scheduled operation, with no exception for operations that limit the guardian itself. setGuardian (line 179) and unpause by the timelock (line 228) are reachable only through schedule -> delay -> execute, and the operation id is public from the Scheduled event. A guardian that watches that event cancels the replacement or the unpause at any moment before execute, so the timelock's two documented remedies never land. docs/SECURITY.md states the guardian is 'limited by: the timelock can replace the guardian and lift a pause'; neither holds. Combined with the guardian's immediate powers this is a single key that halts the protocol for good: pause() closes deposits, publish, activate, anchorReport and executeTrade; revokeSigner/revokeKeeper empty the role table; every re-grant, every unpause and every setGuardian is a timelock operation the guardian vetoes. Only in-kind redemption survives. The brief requires that 'no single wallet should control every function'; a compromised or hostile guardian key controls whether any function other than redeem ever runs again, and the admin has no way back. Access x asymmetry: cancel is immediate and unbounded, while every corrective action is delayed and cancellable. Minimal fix preserving the design: in cancel, when msg.sender is the guardian (not the admin), refuse ids whose target is address(this) and whose data selector is setGuardian or unpause (record target/selector at schedule time, or require the admin to pass target/value/data/salt to cancel and recompute the id). Alternatively, let the admin execute those two operations after a longer fixed delay without veto. Either keeps the guardian's veto over everything that moves funds or widens access.","line":146,"path":"src/TimelockedAdmin.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\n\n/// @dev Finding: the guardian can cancel *any* scheduled operation, including the timelock's own\n/// `setGuardian` and `unpause`. SECURITY.md states the guardian is limited because \"the timelock can\n/// replace the guardian and lift a pause\". It cannot: a guardian that watches `Scheduled` events\n/// cancels its own replacement before `execute`, and the pause it set can never be lifted.\n/// This test fails on the current code (execute reverts NotScheduled after the guardian's cancel)\n/// and passes once the guardian's veto no longer reaches those two operations.\ncontract GuardianVetoTest is Test {\n    address internal constant ADMIN = address(0xA11CE);\n    address internal constant GUARDIAN = address(0x6A4D);\n    address internal constant NEW_GUARDIAN = address(0x6A4E);\n    uint256 internal constant DELAY = 1 days;\n\n    TimelockedAdmin internal tl;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        tl = new TimelockedAdmin(ADMIN, DELAY);\n        _govNow(abi.encodeCall(tl.setGuardian, (GUARDIAN)), bytes32(uint256(1)));\n        assertEq(tl.guardian(), GUARDIAN);\n    }\n\n    function test_timelockCanReplaceGuardianDespiteVeto() public {\n        bytes memory data = abi.encodeCall(tl.setGuardian, (NEW_GUARDIAN));\n        bytes32 salt = bytes32(uint256(2));\n        vm.prank(ADMIN);\n        bytes32 id = tl.schedule(address(tl), 0, data, salt, DELAY);\n\n        // The guardian sees the Scheduled event and vetoes its own replacement.\n        vm.prank(GUARDIAN);\n        try tl.cancel(id) {} catch {}\n\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n        assertEq(tl.guardian(), NEW_GUARDIAN, \"timelock could not replace the guardian\");\n    }\n\n    function test_timelockCanLiftGuardianPauseDespiteVeto() public {\n        vm.prank(GUARDIAN);\n        tl.pause();\n        assertTrue(tl.paused());\n\n        bytes memory data = abi.encodeCall(tl.unpause, ());\n        bytes32 salt = bytes32(uint256(3));\n        vm.prank(ADMIN);\n        bytes32 id = tl.schedule(address(tl), 0, data, salt, DELAY);\n\n        vm.prank(GUARDIAN);\n        try tl.cancel(id) {} catch {}\n\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n        assertFalse(tl.paused(), \"timelock could not lift the guardian's pause\");\n    }\n\n    function _govNow(bytes memory data, bytes32 salt) private {\n        vm.prank(ADMIN);\n        tl.schedule(address(tl), 0, data, salt, DELAY);\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n    }\n}","reproduction":"State: TimelockedAdmin(admin=A, minDelay=1 day); guardian G set via a timelock op. 1) G calls pause(). 2) A calls schedule(tl, 0, abi.encodeCall(setGuardian,(G2)), salt, 1 day) -> Scheduled(id). 3) G calls cancel(id) -> succeeds, readyAt[id]=0. 4) After 1 day A calls execute(tl, 0, same data, salt) -> reverts NotScheduled(). Same for data = abi.encodeCall(unpause,()). Expected per SECURITY.md: the timelock replaces the guardian and lifts the pause after the delay. Actual: both are impossible while G is active; paused stays true, guardian stays G. Repeat for every re-scheduling; G needs one cheap tx per attempt. Scratch test: test/scratch/GuardianVeto.t.sol, both tests fail with NotScheduled() on the current code.","severity":"high","snippet":"    function cancel(bytes32 id) external {\n        if (msg.sender != admin && msg.sender != guardian) revert NotAuthorized();\n        if (readyAt[id] == 0) revert NotScheduled();\n        delete readyAt[id];\n        emit Cancelled(id, msg.sender);\n    }","title":"Guardian can veto its own replacement and the timelock's unpause, making the guardian irremovable and any pause or role revocation permanent"},{"citation":"resolved","description":"IndexVault._balanceOf (lines 326-330) reads a held token's balanceOf with {gas: TRANSFER_GAS} precisely so that a token which 'burns gas' (a listed basket-token threat in docs/SECURITY.md, 'limited by gas-capped calls') cannot block redemptions. RebalanceExecutor._nav performs the same read with a plain staticcall that forwards 63/64 of all remaining gas. _nav runs at the top of _authorize for every executeTrade, including exits of other tokens and the exit of the burning token itself. A held token whose balanceOf consumes everything it is given (an upgraded or malicious member) therefore leaves 1/64 of the transaction budget; with a mainnet-sized 30M budget that is ~470k, less than what the swap plus IndexVault.endTrade need (endTrade alone spends TRANSFER_GAS=500k on the same token). Every keeper trade of every member dies out of gas. Quarantining the token (the documented playbook) does not help: _nav still calls its balanceOf before checking isQuarantined. disposeQuarantined bypasses _nav, but after the timelock sells the position, IndexVault._balanceOf reports readable=false so the token is never dropped from _held, and _nav keeps burning on it forever. Result: the executor can never trade again; the only repair is deploying and wiring a fixed executor. Deposits are already closed by the vault (unpriced holding), redemption still works. Asymmetry: the same read is guarded in one contract and unguarded in its pair. Minimal fix: cap the staticcall in _nav (and the balanceOf in _valueOf, line 299) with the same TRANSFER_GAS, treating a failed read as unpriced exactly as the vault does; optionally let endTrade drop a quarantined, unreadable token with zero balance after disposal.","line":310,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket {PLAIN 50%, BURN 50%}, vault NAV 1,000,000 USDC, both members bought to target, BURN then switches its balanceOf to `assembly { invalid() }`; guardian calls registry.quarantine(BURN). PLAIN's feed moves so PLAIN is 2x over target (material sell). Keeper calls executor.executeTrade{gas: 30_000_000}(PLAIN, USDC, 100e18, 0, router, router.swap(PLAIN, USDC, 100e18, fair)). Expected: success=true, PLAIN sold toward target. Actual: the staticcall at line 310 burns 63/64 of the budget, swapThroughRouter runs with the remainder, IndexVault.endTrade hits ReentrancySentryOOG, the catch branch emits TradeFailed and the outer call then reverts OutOfGas (the healthy token PLAIN is also the one charged with the failure). vault.redeem{gas: 3_000_000}(1, ALICE, false) in the same state succeeds, showing the vault's capped read survives. Scratch test: test/scratch/ExecutorGasBurn.t.sol fails with EvmError: Revert at ~31.4M gas; the identical test with burn=false passes.","severity":"medium","snippet":"            (bool ok, bytes memory ret) = held[i].staticcall(abi.encodeCall(IERC20.balanceOf, (address(vault))));","title":"RebalanceExecutor._nav reads held-token balances without a gas cap, so one gas-burning basket token permanently disables every keeper trade (the vault caps the same read)"},{"citation":"resolved","description":"RebalanceExecutor._valueOf uses IERC20(token).balanceOf(vault) regardless of whether the vault tracks the token, while IndexVault._nav, redeem, previewRedeem and the executor's own _nav iterate only _held, which grows solely through endTrade. A basket member that was never bought, or that was dropped after being sold to zero, is therefore priced into 'current' by the executor but into nothing by the vault. Anyone can transfer such a token to the vault: the buy branch of _authorize then reverts NothingToTrade (current >= target) and the sell branch reverts NotHeld, so the keeper cannot bring that member into the basket, NAV ignores the balance, and redeemers receive none of it. Only the timelock's rescue (not asset, not held) can move it, after minDelay. The cost to the griefer is the donated tokens (they end up with the treasury), which bounds the severity. Minimal fix: have _valueOf consult vault.isHeld and treat untracked balances as zero (consistent with _nav), or let the vault adopt an untracked member balance into _held via endTrade/beginTrade so NAV and redemption see what the executor sees.","line":298,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket {A 50%, B 50%}, vault holds 1,000,000 USDC and nothing else; executor.position(A) gives current=0, target=490,000 USDC. Mallory transfers 4,900e18 A (A/USD feed = 100) straight to the vault. vault.nav() still returns 1,000,000e6 and isHeld(A) is false; executor.position(A).current is now >= target. Keeper executeTrade(USDC, A, 10_000e6, ...) reverts NothingToTrade(); executeTrade(A, USDC, 1e18, ...) reverts NotHeld(A); previewRedeem returns only USDC. Expected: the keeper can fill A's deficit (or the donation counts in NAV and redemptions). Scratch test test/scratch/Leads.t.sol::test_leadA_donationBlocksBuyOfMember demonstrates all three reverts/omissions on the current code.","severity":"low","snippet":"    function _valueOf(address token) private view returns (uint256 value) {\n        uint256 balance = IERC20(token).balanceOf(address(vault));\n        if (token == reserve || balance == 0) return balance;\n        (value,) = registry.convert(token, balance, reserve);\n    }","title":"Executor values a member by the vault's raw balance while the vault's NAV and redemptions only count the held list: tokens sent directly to the vault block buying that member and are invisible to hold"},{"citation":"resolved","description":"executeTrade increments failureCount[token] on every caught failure and quarantines once it reaches FailureThreshold; the counter is cleared only by a successful trade of that token. AssetRegistry.releaseQuarantine (timelock, after minDelay and guardian review) clears the quarantine flag but the executor's counter stays at the threshold. The next failed trade, including a keeper's gas-underfunded or mis-routed call, trips the `failures >= threshold && !isQuarantined` branch at once. The documented 'three consecutive failures' rule degrades to one after any release, and the timelock cannot reset the counter (no setter). Asymmetry between the writer that sets the condition (executor) and the writer that lifts it (registry). Minimal fix: in executeTrade, treat `failures >= threshold` only when the count was accumulated since the last release (e.g. reset the counter when registry.isQuarantined(token) is false and the stored count already exceeds the threshold, or expose a timelock-only reset called alongside releaseQuarantine).","line":124,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket {A, B}, both bought; B's feed quadruples so B is over target. Keeper submits three executeTrade(B, USDC, 1e18, 0, router, router.fail()) calls: each returns success=false, the third auto-quarantines B and failureCount(B)=3. Timelock executes registry.releaseQuarantine(B) after the delay: isQuarantined(B)=false, failureCount(B) still 3. One more failing executeTrade(B, ...) -> isQuarantined(B)=true again. Expected: three new consecutive failures are required. Scratch test test/scratch/Leads.t.sol::test_leadB_failureCountSurvivesRelease demonstrates it on the current code.","severity":"low","snippet":"            uint256 failures = ++failureCount[token];\n            if (failures >= registry.param(Param.FailureThreshold) && !registry.isQuarantined(token)) {","title":"failureCount is not reset by releaseQuarantine, so one failed trade after a release re-quarantines the token although FailureThreshold is 3"}],"hash":"60293c7c3738a8d5a1afb8aaa6640b5ae22d1eb582437b63ed166d3c52724b48","nodeId":"77ef22ee-68fd-45cc-81de-63d380cf6ca1","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` in the repository root, and no tracked file was modified. The only new paths are the findings file and my `test/scratch/` directory, both of which git ignores.\n\n**Outcome: 4 substantiated findings, 60 coverage rows (54 hold, 6 point at findings, none unreached).**\n\nFindings, most severe first:\n\n1. **High. The guardian can veto its own replacement and the timelock's unpause.** `TimelockedAdmin.cancel` has no exception for operations that limit the guardian, and those operations only reach the chain through a schedule-delay-execute path whose id is public. A hostile or compromised guardian key can pause, revoke every signer and keeper, and then cancel every re-grant, unpause and `setGuardian` forever. Only in-kind redemption survives. The security document claims the opposite limit. Proof test attached; it fails today with `NotScheduled()`.\n\n2. **Medium. The executor's `_nav` reads held-token balances with an uncapped staticcall, while the vault caps the identical read.** A basket token whose `balanceOf` burns gas, which the trust model names as a threat, leaves the keeper's call with 1/64 of its budget, and every trade of every member then dies out of gas. Quarantine does not help, and after disposal the token is never dropped from the held list, so the executor is bricked until replaced. Proof test attached with a 30M gas budget; the same test with the burn switched off passes.\n\n3. **Low. Executor and vault disagree on what a member position is.** The executor prices by raw vault balance, the vault only by its held list. A stranger who transfers a target's worth of a member token to the vault blocks the keeper from buying it, and holders never see it. The griefer loses the tokens to the timelock's rescue, which bounds the impact.\n\n4. **Low. `failureCount` is not reset by `releaseQuarantine`**, so after a release one failed trade re-quarantines the token instead of three.\n\n**Coverage.** I traced all 57 listed entry points plus three invariants (role exclusivity, executor-only asset movement, delta/buffer/window/floor on trades). The Access Control, Trust Gap and Asymmetry guides were applied in full to the timelock, registry, epoch manager, vault, executor, waterfall and hook. Leads I examined and dropped because the design intends them or the trigger is purely privileged: recipient changes not settling accrued protocol buckets, permissionless stale-feed quarantine followed by a keeper exit sale, and keeper-forced quarantine via underfunded gas. Not covered: economic correctness of fee math and NAV rounding, which belong to other specialists.","treeHash":null,"usage":{"cachedInputTokens":2714380,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":64110,"runtime":"claude","turns":39,"wallClockMs":888560}},{"artifacts":[],"attempt":1,"bundleHash":"cffe8780d744e96fde4a9d31930d24e64b6b70d1fd677532eaeda9a2a0a52f32","device":"6e1819675bb7567a","findings":[],"hash":"61d4968ffe0afdfc5c1ea11a495540d561c50f5199fbac60e5be9dfd227f1cb3","nodeId":"abc6922f-88dc-4772-a2e0-5d80a0d81188","outcome":"completed","summary":"Fixed 16 findings and recorded all 17 verdicts in `.imd-responses.json`.\n\nOne advisory is disputed: deposits remain closed for unpriceable, untransferable holdings to preserve existing holders’ claims. The limitation is documented.\n\nValidation passed: `forge build`, `forge fmt --check`, and all 251 tests, including supplied proofs, fuzz tests, and invariants.","treeHash":"99b9c6beb0326a2f1361807c502c1d470e1e5420","usage":{"cachedInputTokens":1942144,"inputTokens":127260,"model":"gpt-6-astra","outputTokens":24343,"runtime":"codex","turns":7,"wallClockMs":716608}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7b7c27166477ddd8","findings":[],"hash":"6583f05126ce77d63313b3f76667a2e60162711e693fd095fdc6195f403bbcb1","nodeId":"e99918fb-1eef-464c-a358-fddddeb2126a","outcome":"failed","summary":"the task produced no changes; the agent's last message was: The execution environment blocked all commands with `bwrap: setting up uid map: Permission denied`, including a basic `ls`.\n\nI couldn’t read the implementation or supplied checks, write meaningful tests, or run `forge build` and `forge test`. No files were changed, and no contract findings were reported.\n\nThis task needs a rerun with a working command sandbox.","treeHash":null,"usage":{"cachedInputTokens":51712,"inputTokens":9452,"model":"gpt-6.1-sol","outputTokens":460,"runtime":"codex","turns":2,"wallClockMs":16926}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1ca477e8d9b58040","findings":[{"citation":"resolved","description":"Settled from write_foundry_tests (reported medium; recalibrated to low). README line 185 and the contract NatSpec (line 14) say the executor buys 'only for eligible members'; `_authorize` enforces membership (targetWeightBps != 0), approval, confirmed/auto quarantine and, through `registry.convert`, a fresh price, but never `registry.isEligible`, so the one remaining eligibility rule, MinTokenAge, is not applied on purchase. After a timelocked `setParam(MinTokenAge, 90 days)` that makes an active member ineligible (isEligible == false), a keeper can still buy it up to its target inside the window. The same proof shows a re-approval that lowers `maxWeightBps` from 2000 to 100 does not shrink the live target either; that half is a design choice (the active basket's signed weights, already checked at activation, are the targets) rather than a documented rule, and it is noted here only for completeness. Impact is bounded: both preconditions are deliberate timelock actions, the same timelock can `revokeToken` to zero the target at once, activation re-checks eligibility so the next epoch cannot carry the token, and no funds leave the system. Minimal fix preserving the design: in the buy branch replace the approval check with `if (!registry.isEligible(buyToken)) revert NotBuyable(buyToken);` (isEligible already implies approved, not quarantined and fresh-priced).","line":233,"path":"src/RebalanceExecutor.sol","reproduction":"Ran .imd/reads/proofs/Proof_a83a2cedbc59.t.sol as test/scratch/Proof_a83a2cedbc59.t.sol: `forge test --match-path test/scratch/Proof_a83a2cedbc59.t.sol` -> both tests FAIL 'purchase bypassed the live risk rule'. State: TimelockedAdmin(minDelay 1 day), one approved 18-decimal token listedAt = now - 31 days with a fresh $1 feed, 100,000 USDC deposited, epoch 1 activated with the token at 2000 bps. Timelock schedules and executes setParam(MinTokenAge, 90 days) (precondition assert registry.isEligible(token) == false passes). Inside the rebalance window the keeper calls executeTrade(USDC, token, 10_000e6, 0, router, router.swap(...)). Expected: NotBuyable / (false, 0) and the vault keeps 100,000 USDC. Actual: returns (true, 10_000e18); vault reserve 90,000 USDC and 10,000 tokens held.","severity":"low","snippet":"            if (epochs.targetWeightBps(buyToken) == 0 || !registry.isApproved(buyToken)) revert NotBuyable(buyToken);\n            if (registry.isQuarantined(buyToken) || registry.isAutoQuarantined(buyToken)) revert NotBuyable(buyToken);","title":"Executor buy path checks approval and quarantine but not current eligibility, so a member the timelock has aged out (MinTokenAge raised) can still be bought until the next epoch"},{"citation":"resolved","description":"Settled from write_foundry_tests (reported medium; recalibrated to low). README 'Hard rules' (line 170) lists 'attested market cap, liquidity and volume at or above the on-chain minimums' among the rules enforced 'when a proposal is published and again when it is activated'. `Pending` stores neither the attested figures nor a risk-parameter version, so `activate` re-checks expiry, snapshot age, interval, signer set, methodology, eligibility and weight caps only; a timelocked increase of MinMarketCapUsd/MinLiquidityUsd/MinVolumeUsd that lands during the proposal delay does not stop the proposal. Impact is low: the figures are signer-attested in the first place (a quorum that wants to pass a sub-minimum asset can simply attest a larger number), the timelock change is visible at least minDelay before it executes, the guardian can `cancelPending`, and the next proposal is checked against the new minimums at publish. Minimal fix: keep the three attested arrays in `Pending` and repeat the `BelowMinimums` comparison in `activate`, or bump a registry `rulesVersion` in `setParam` for the three minimums and commit the pending proposal to it.","line":197,"path":"src/EpochManager.sol","reproduction":"Ran .imd/reads/proofs/Proof_0633c23bdf64.t.sol as test/scratch/Proof_0633c23bdf64.t.sol: `forge test --match-path test/scratch/Proof_0633c23bdf64.t.sol` -> FAIL 'activated $1bn asset under the current $2bn minimum'. State: default minimums (MinMarketCapUsd 250,000,000), quorum 1. Timelock schedules setParam(MinMarketCapUsd, 2_000_000_000) with a 1-day delay. After 20 hours a valid quorum-signed epoch-1 proposal attesting marketCapsUsd = 1,000,000,000 is published (accepted: above the 250m minimum then in force). After 6 more hours the timelock executes the parameter change (minimum now 2bn) and anyone calls activate(): snapshot 6h old, feeds fresh, expiry 17h away. Expected: revert BelowMinimums(token) and epoch() == 0. Actual: activation succeeds, epoch() == 1 with the asset attested at half the current minimum.","severity":"low","snippet":"            if (!registry.isEligible(token)) revert NotEligible(token);\n            if (q.weightsBps[i] > registry.maxWeightBps(token)) revert OverWeight(token);","title":"activate() does not re-check the attested market cap/liquidity/volume against the current minimums, although the README says every hard rule is enforced again at activation"}],"hash":"6ddb3448b40bec7c23df2a618ee7ab4593798f1769163b1e263da05eb4a828e9","nodeId":"c4c8b65f-1a08-4b70-9b30-8abf9ae97ad4","outcome":"completed","summary":"Review complete. `.imd-findings.json` holds the result; no project files were changed (scratch tests removed, tree clean).\n\n## Settlement of my earlier findings\n\nAll eight blocking findings are **fixed**, each confirmed by running the attached proofs against the revised code (every one now passes) and by tracing the changed lines:\n\n| Finding | Verification |\n|---|---|\n| Guardian vetoes own replacement/unpause (high) | `schedule` records zero-value self-calls to `setGuardian`/`unpause` as `recoveryOperation`; guardian `cancel` reverts `RecoveryCannotBeVetoed`; admin cancel stays. Both proofs pass. |\n| Hook fee on unfilled volume | `afterSwap` compares raw pool delta with `amountSpecified + fee` and reverts `PartialFill`; checked against `Hooks.afterSwap` in the vendored v4-core (raw delta is passed). |\n| strict redeem forfeits unreadable slice | `readable` now checked before rounding; strict reverts, non-strict emits `RedemptionSkipped(…, 0)`. Proof passes. |\n| `activate` ignores MaxSnapshotAge | Re-checked at line 184. Proof passes. |\n| Keeper self-quarantine → full liquidation | Executor path sets only `isAutoQuarantined` (blocks buys); `_target`, `isEligible`, vault NAV unaffected. |\n| Same-block deposit/redeem oracle-lag arb | `_update` override locks shares minted this block for burn/transfer/transferFrom; older shares stay liquid. |\n| Quarantined token priced into deposit NAV | Vault `_nav` marks a nonzero confirmed-quarantined holding as unpriced → deposits close. |\n| Uncapped `balanceOf` in executor `_nav` | 500k gas cap, quarantined tokens skipped before the read. |\n\nThe eight fixed advisories (empty basket, window anchor, failure-streak reset, dust exit, untracked donation, push access, ProposalDelay bound, reserveDecimals check) all verified in the diff and the `Revision.t.sol` regressions. The disputed advisory (fail-closed deposits on a dead untransferable position) I accept as a documented design decision and drop. Full suite: 267 passed, 0 failed.\n\n## New this round (from the independent tester, both reproduced, both recalibrated to **low**)\n\n1. **`RebalanceExecutor` buy path doesn't apply `isEligible`** (`src/RebalanceExecutor.sol:233`) — README/NatSpec say \"buys only for eligible members\", but only approval/quarantine/price are checked, so a member aged out by a timelocked `MinTokenAge` increase can still be bought until the next epoch. Preconditions are deliberate timelock actions, `revokeToken` is the immediate remedy, no funds leave — hence low, not medium.\n2. **`activate()` doesn't re-check attested market minimums** (`src/EpochManager.sol:197`) — README says the hard rules run \"again when it is activated\", but `Pending` keeps no market data, so a minimum raised during the delay doesn't bite. Signers attest those numbers anyway and the guardian can `cancelPending`, so low.\n\nNothing in the fix commit introduced a new reproducible defect; the one operational consequence I noticed (a proposal published too early for the interval boundary will go stale and occupy the pending slot until expiry or guardian cancel) is signer-controlled and recoverable, so I did not report it.","treeHash":null,"usage":{"cachedInputTokens":3098218,"inputTokens":46,"model":"claude-fable-5-1","outputTokens":28586,"runtime":"claude","turns":24,"wallClockMs":431134}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ce823f211d29a38d","findings":[],"hash":"6ecafb9bb4e5eb7370e757e92fe88d6aea3c6761418ef5513081978a4ff78d04","nodeId":"e99918fb-1eef-464c-a358-fddddeb2126a","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":1026}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ef31844bb462de78","findings":[],"hash":"7614bf4e16ae5759c78ea5b420051172e4d141587bb2942bce2a36682197bc4f","nodeId":"4f000c45-6ee2-4237-84c9-796399ac0390","outcome":"failed","summary":"runtime reported <synthetic>, not the required premium model claude-fable-5-1","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"<synthetic>","outputTokens":0,"runtime":"claude","turns":1,"wallClockMs":1735}},{"artifacts":[],"attempt":1,"bundleHash":"ea7713811656e35bae659c83416615077415e6bc2e7952e1365207ad46604a71","device":"d2bae9a4a05e8265","findings":[{"description":"redeem discards the readable flag from _balanceOf. A failed or malformed balance read becomes zero, so the loop continues before strict is checked. The transaction burns the caller's shares and pays only the readable assets even when strict=true. The holder permanently loses the claim to the skipped asset if its balance function subsequently recovers. Require an unreadable holding to revert in strict mode; retain the explicit non-strict escape path.","line":161,"path":"src/IndexVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {IndexVault} from \"src/IndexVault.sol\";\n\ncontract RedemptionProofToken {\n    mapping(address => uint256) private balances;\n    mapping(address => mapping(address => uint256)) public allowance;\n    bool public broken;\n    function decimals() external pure returns (uint8) { return 6; }\n    function mint(address to, uint256 amount) external { balances[to] += amount; }\n    function breakBalance() external { broken = true; }\n    function balanceOf(address who) external view returns (uint256) {\n        require(!broken, \"balance temporarily unavailable\"); return balances[who];\n    }\n    function approve(address to, uint256 amount) external returns (bool) { allowance[msg.sender][to] = amount; return true; }\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balances[msg.sender] -= amount; balances[to] += amount; return true;\n    }\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount; balances[from] -= amount; balances[to] += amount; return true;\n    }\n}\ncontract RedemptionProofExecutor {\n    function track(IndexVault vault, address token) external {\n        vault.beginTrade(vault.asset(), 0); vault.endTrade(token);\n    }\n}\ncontract StrictRedemptionProof is Test {\n    TimelockedAdmin admin;\n    function configure(address target, bytes memory data) internal {\n        bytes32 salt = keccak256(data);\n        admin.schedule(target, 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        admin.execute(target, 0, data, salt);\n    }\n    function test_strictRedemptionMustPreserveClaimOnUnreadableAsset() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(address(this), 1 days);\n        RedemptionProofToken reserve = new RedemptionProofToken();\n        RedemptionProofToken held = new RedemptionProofToken();\n        AssetRegistry registry = new AssetRegistry(address(admin), address(reserve), 6);\n        IndexVault vault = new IndexVault(address(admin), address(registry), address(reserve), 6, 1_000_000e6);\n        RedemptionProofExecutor executor = new RedemptionProofExecutor();\n        configure(address(admin), abi.encodeCall(admin.setGuardian, (address(0xBEEF))));\n        configure(address(admin), abi.encodeCall(admin.setExecutor, (address(executor))));\n        reserve.mint(address(this), 1000e6);\n        reserve.approve(address(vault), 1000e6);\n        uint256 shares = vault.deposit(1000e6, address(this), 0);\n        held.mint(address(vault), 1000e6);\n        executor.track(vault, address(held));\n        assertTrue(vault.isHeld(address(held)));\n        held.breakBalance();\n        (bool ok,) = address(vault).call(abi.encodeCall(vault.redeem, (shares, address(this), true)));\n        assertFalse(ok, \"strict redemption burned shares without paying the held asset\");\n        assertEq(vault.balanceOf(address(this)), shares, \"claim must survive a strict failure\");\n    }\n}","reproduction":"Run StrictRedemptionProof from the proof source. Deposit 1,000 units of a six-decimal reserve, track a second token with a nonzero vault balance, make only that token's balanceOf revert, then redeem all shares with strict=true. Expected: revert and preserve the share balance. Actual: redeem succeeds, burns every share, pays the reserve, and leaves the second asset unpaid. Observed failure: strict redemption burned shares without paying the held asset.","severity":"medium","title":"Strict redemption silently forfeits assets whose balanceOf fails"},{"description":"Snapshot freshness is checked only by publish. activate checks expiry and current token feeds but never compares the stored snapshotTime with MaxSnapshotAge. With the default one-day freshness limit and a three-day expiry, a previously published proposal activates after 25 hours despite stale research. A seven-day expiry can extend this further. The local methodology describes the publication-only check, but this leaves the requested rejection of stale proposals and hard exclusion of stale data unenforced at activation. Recheck snapshot age before committing the basket; an unexpired signature is not evidence of fresh research.","line":183,"path":"src/EpochManager.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {EpochManager} from \"src/EpochManager.sol\";\n\ncontract FreshProofFeed {\n    function decimals() external pure returns (uint8) { return 8; }\n    function latestRoundData() external view returns (uint80,int256,uint256,uint256,uint80) {\n        return (1, 1e8, block.timestamp, block.timestamp, 1);\n    }\n}\ncontract ProofAsset { function decimals() external pure returns (uint8) { return 18; } }\ncontract StaleActivationProof is Test {\n    TimelockedAdmin admin;\n    AssetRegistry registry;\n    EpochManager epochs;\n    function configure(address target, bytes memory data) internal {\n        bytes32 salt = keccak256(data);\n        admin.schedule(target, 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        admin.execute(target, 0, data, salt);\n    }\n    function test_expiryMustNotOverrideSnapshotFreshness() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(address(this), 1 days);\n        registry = new AssetRegistry(address(admin), address(0xCA01), 6);\n        epochs = new EpochManager(address(admin), address(registry));\n        ProofAsset token = new ProofAsset();\n        FreshProofFeed feed = new FreshProofFeed();\n        configure(address(admin), abi.encodeCall(admin.setSigner, (vm.addr(123), true)));\n        configure(address(admin), abi.encodeCall(admin.setQuorum, (1)));\n        configure(address(registry), abi.encodeCall(registry.approveToken,\n            (address(token), address(feed), 1 hours, 2000, uint40(block.timestamp - 40 days), keccak256(\"review\"))));\n        EpochManager.Proposal memory p;\n        p.epoch = 1;\n        p.snapshotTime = uint64(block.timestamp);\n        p.expiry = uint64(block.timestamp + 3 days);\n        p.methodologyVersion = 1;\n        p.signerSetVersion = admin.signerSetVersion();\n        p.dataHash = keccak256(\"daily research\");\n        p.tokens = new address[](1); p.tokens[0] = address(token);\n        p.weightsBps = new uint16[](1); p.weightsBps[0] = 2000;\n        p.marketCapsUsd = new uint256[](1); p.marketCapsUsd[0] = 1_000_000_000;\n        p.liquidityUsd = new uint256[](1); p.liquidityUsd[0] = 50_000_000;\n        p.volumesUsd = new uint256[](1); p.volumesUsd[0] = 50_000_000;\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(123, epochs.hashProposal(p));\n        bytes[] memory sigs = new bytes[](1); sigs[0] = abi.encodePacked(r,s,v);\n        epochs.publish(p, sigs);\n        vm.warp(block.timestamp + 25 hours);\n        assertTrue(registry.isEligible(address(token)), \"price remains fresh\");\n        assertLt(block.timestamp, p.expiry, \"proposal has not expired\");\n        vm.expectRevert(EpochManager.StaleSnapshot.selector);\n        epochs.activate();\n    }\n}","reproduction":"Run StaleActivationProof from the proof source. Configure a real timelock, registry and signer quorum; approve a token older than 30 days whose feed always returns the current timestamp. Publish a valid epoch-1 proposal at time T with snapshotTime=T and expiry=T+3 days. Warp to T+25 hours and activate. Expected: StaleSnapshot and no epoch change. Actual: activation succeeds and epoch becomes 1. Observed failure: next call did not revert as expected.","severity":"medium","title":"Activation accepts a research snapshot older than MaxSnapshotAge"}],"hash":"940f9713e241c84880794329bd0353c000a49bc55fd90d6d926b5f16b328924d","nodeId":"e99918fb-1eef-464c-a358-fddddeb2126a","outcome":"completed","summary":"Added adversarial tests and randomized custody/accounting invariants.\n\n`forge build` and `forge test` pass: **246 tests, zero failures**.\n\nReported two defects with failing proofs in [.imd-findings.json](/home/imd/.identitymd/work/6321056b-a125-48f2-9c7a-81cc8695a1f2/e99918fb-1eef-464c-a358-fddddeb2126a/.imd-findings.json):\n\n- Strict redemption can silently forfeit unreadable assets.\n- Stale research snapshots can activate.\n\nMainnet-fork validation remains outstanding.","treeHash":"04ddacef66a0277c41ac4d6ecb8943d102e67ce4","usage":{"cachedInputTokens":2615680,"inputTokens":157389,"model":"gpt-6-astra","outputTokens":25734,"runtime":"codex","turns":6,"wallClockMs":860825}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"bdd9b74dce66953d","findings":[{"citation":"resolved","description":"When the quote currency is the specified side of a swap (buy exact-input, sell exact-output), `beforeSwap` computes `fee = specified * hookFeePips / PIPS` from `params.amountSpecified` and takes it from the PoolManager before the pool runs. The pool may fill only part of the request: the swap reaches `sqrtPriceLimitX96`, or the active liquidity range is exhausted (the default limits of every v4 router). In v4 the trader's delta is `swapDelta - hookDelta` (lib/v4-core/src/libraries/Hooks.sol:316), so the full-request fee stays charged no matter how little traded. `afterSwap` does this correctly for the unspecified side (it reads `swapDelta`), so the two halves of the hook disagree.\n\nEconomics (real PoolManager, 1e24 liquidity in ticks -6000..6000, 1% total fee, 25% to LPs):\n- Sell IMDEX for exactly 1,000 quote with a price limit 1e-6 above spot: the pool pays out 0.99999 quote, the hook takes 7.5 quote. The trader ends the swap with -6.5 quote AND -1.0025 IMDEX: they paid both currencies and received nothing. Effective fee 750%.\n- Buy IMDEX with 1,000,000 quote exact-input and the default price limit: the range absorbs 350,715 quote, the hook takes 7,500 (2.14% of what traded, 4,870 more than the 0.75% owed on the filled part). The overcharge is paid to the waterfall and split into the buckets, i.e. funds paid to the wrong party.\n\nRouters that enforce a strict min-output on the quote leg will revert such swaps; routers or direct `unlock` callers with a loose minimum, and every exact-input quote swap whose min-out is met, pay the overcharge. The brief asks the hook to 'collect the project-token swap fee'; a fee on volume that never traded is not that.\n\nFix (keeps the design): charge the quote-specified fee on the filled amount. Either (a) in `afterSwap`, compare `|swapDelta.specified|` with the amount the pool was asked for (`amountSpecified ± fee`) and revert on a partial fill (`PartialFillNotSupported`), so a swapper never pays for an unfilled request; or (b) take only a provisional fee in `beforeSwap` and refund the difference on the unspecified side in `afterSwap` (negative `hookDeltaUnspecified`), documenting that the refund arrives in the other currency.","line":140,"path":"src/FeeHook.sol","reproduction":"State: hooked pool initialised at price 1:1 with liquidity 1e24 between ticks -6000 and 6000; swapFee 1% (lp 25%, hook 0.75%). Trader holds 100,000 IMDEX and 100,000 quote.\nCall: PoolManager.swap(key, {zeroForOne: false, amountSpecified: +1000e18 (exact-output quote), sqrtPriceLimitX96: SQRT_PRICE_1_1 * 1.000001}) through any unlock router.\nExpected: fee = 0.75% of the quote actually delivered (~0.0075 quote) or the swap refused.\nActual: waterfall receives 7.5 quote; trader's quote balance changes by -6.5 quote and IMDEX by -1.0025; totalFeesCollected += 7.5e18.\nSecond shape: swap(key, {zeroForOne: true, amountSpecified: -1_000_000e18, limit MIN_SQRT_PRICE+1}): pool consumes 350,715 quote, fee taken 7,500 instead of <= 2,650.\nRun: forge test --match-path test/scratch/HookPartialFill.t.sol -vv (both tests fail on the current code).","severity":"medium","snippet":"            delta = toBeforeSwapDelta(_collect(sender, specified, q, params.zeroForOne), 0);","title":"FeeHook charges the quote-specified fee on the requested amount, not on what the pool filled"},{"citation":"resolved","description":"The executor's three limits on a keeper (delta only, drift threshold, rebalance window) all hang on `exit = target == 0`, and `_target` returns 0 for any quarantined token. The executor itself quarantines a token after `FailureThreshold` (3) consecutive failed swaps (src/RebalanceExecutor.sol:124-127), and a failure is anything the keeper's own router calldata makes revert. Failures are counted per call with no time or block spacing, so a keeper submits three `router.fail()` calls per member in one block, every member becomes quarantined, and `exit` then allows `sellAmount <= balance` for each of them with no window, no drift check and no active-basket requirement (lines 254-259). Each sale only has to clear the oracle floor less `MaxSlippageBps` (1%), and the keeper chooses the venue.\n\nThe only precondition for the failing trades is that a buy of each member is authorised at that moment (material deficit inside the window). The keeper manufactures that too: depositing 15% of NAV into the vault lifts every target by 2.94% of NAV, above the 2.5% drift threshold. A new epoch or any sizeable deposit gives the same opening.\n\nEconomics (1,000,000 USDC vault, five members at 19.6% each, keeper deposits 150,000):\n- 15 failing calls, then 5 exit sales filled at 99% of oracle through the keeper's venue.\n- NAV 1,150,000 -> 1,140,200 USDC: 9,800 USDC (1% of the 980,000 invested) goes to the venue, i.e. the keeper's counterparty; the vault is 100% reserve, zero held tokens, and every member is quarantined, so nothing can be bought back until the timelock releases each one (>= 1 day each) and a window opens.\n\nSECURITY.md bounds the keeper to 'up to MaxSlippageBps of each allowed trade' and README says a keeper cannot 'exceed the delta'; here the keeper enlarges 'allowed' to the entire basket and converts the index to cash. The guardian can revoke the keeper afterwards, but the sequence completes in one block.\n\nFix (keeps the design): a quarantine set by the executor should not by itself grant exit rights. For example, keep an `autoQuarantined[token]` flag in the executor and treat such tokens as not buyable but with their basket target intact (sells stay bounded by the excess over target and by the window) until the guardian or timelock confirms the quarantine; or count a failure only once per block/interval and require the failures to be spread over `FailureThreshold` distinct windows.","line":244,"path":"src/RebalanceExecutor.sol","reproduction":"State: Alice deposits 1,000,000 USDC; epoch 1 activates five tokens at 2000 bps; keeper buys each to target at oracle price (window open). Keeper then deposits 150,000 USDC so each member's deficit (29,400) exceeds the 2.5% drift threshold (28,750).\nCalls (all by KEEPER, same block): for each member, 3x executeTrade(usdc, member, 1000e6, 0, venue, abi.encodeCall(venue.fail, ())) -> each returns (false, 0); after the third, registry.isQuarantined(member) == true. Then for each member executeTrade(member, usdc, vault balance, 0, venue, swap filled at 99% of oracle) -> (true, ...).\nExpected: a position at its target cannot be sold at all, and never outside the delta.\nActual: vault.heldTokens().length == 0, usdc.balanceOf(vault) == 1,140,200e6, NAV fell 9,800 USDC.\nRun: forge test --match-path test/scratch/KeeperLiquidation.t.sol -vv (fails on the current code).","severity":"medium","snippet":"            exit = target == 0;","title":"A keeper can quarantine every member with its own failing swaps and liquidate the whole basket in one block at the slippage floor"},{"citation":"resolved","description":"Deposits mint shares at `navBefore`, the sum of held balances valued at each Chainlink feed's last answer; redemptions pay a pro-rata slice in kind and need no price. Nothing links the two in time: `deposit` and `redeem` are separate `nonReentrant` calls and can run back to back in one transaction, there is no cooldown, and `DepositFeeBps` defaults to 0. A feed is 'fresh' for the registry as long as it is inside its heartbeat, but Chainlink only pushes a new answer when the price moves by the feed's deviation threshold (0.5%-2% for the ETH-ecosystem USD feeds this index would use). Whenever the market is up by less than that threshold, NAV is stale-low and the round trip deposit -> redeem -> sell is a riskless, flash-loanable transfer from existing holders (including the treasury's shares) to the caller.\n\nEconomics (1,000,000 USDC vault fully invested, five members up 1.9% on the market, feeds unchanged):\n- Attacker deposits 1,000,000 USDC at the stale NAV and immediately redeems: receives 510,000 USDC plus half of every position.\n- At the true price the attacker holds 1,009,310 USDC of value for 1,000,000 deposited (+0.93%). Alice's slice falls from 1,018,620 to 1,009,310: she loses the same 9,310 USDC.\n- Profit scales with deposit size up to NAV (deposit = NAV captures half the lag); the deposit cap bounds it per vault, not per attacker, and the cap is the beta size. The maximum `DepositFeeBps` (100) does not cover a 2% threshold feed, and the default (0) covers nothing.\n\nREADME names feed latency as a known limitation with the cap and the optional fee as mitigations; the defect reported here is that the arbitrage is atomic and needs no capital or risk, which those two mitigations do not address.\n\nFix (keeps the design): record `lastDepositBlock[receiver]` (or timestamp) in `deposit` and refuse `redeem`/`transfer` of shares minted in the same block, or impose a short minimum holding period; optionally set a non-zero default `DepositFeeBps` at or above the largest configured feed deviation.","line":134,"path":"src/IndexVault.sol","reproduction":"State: Alice deposited 1,000,000 USDC; basket of five tokens bought at $100 each (feeds at 100e8, fresh). Market price of each token is now $101.90 but no feed has crossed its 2% deviation, so every feed still answers 100e8 with updatedAt == block.timestamp.\nCalls (ATTACKER, one transaction): usdc.approve(vault, 1e12); shares = vault.deposit(1e12, ATTACKER, 0); vault.redeem(shares, ATTACKER, true).\nExpected: a deposit immediately redeemed returns at most what was deposited (the vault's own fuzz test asserts this at an unchanged price).\nActual: attacker holds 510,000 USDC + 980.96 of each token; valued at $101.90 that is 1,009,310 USDC (> 1,000,000). previewRedeem(Alice) at the true price drops from 1,018,620 to 1,009,310 USDC.\nRun: forge test --match-path test/scratch/OracleLagRoundTrip.t.sol -vv (fails on the current code).","severity":"medium","snippet":"        shares = Math.mulDiv(received - fee, totalSupply() + VIRTUAL_SHARES, navBefore + 1);","title":"Same-block deposit at feed NAV and in-kind redemption lets anyone take the feeds' lag from existing holders"},{"citation":"resolved","description":"`IndexVault._nav` treats every held token with a non-zero balance and no fresh price as `unpriced`, and `deposit` reverts with `PriceUnavailable` for it (line 125), even when the token is quarantined. The executor's `_nav` deliberately counts a quarantined unpriced token as zero so the rest of the basket can trade, but the vault does not, so the two disagree. A token is only dropped from `_held` by `endTrade` when its balance reads zero, and the only ways to empty it are a keeper sale (needs a price) or the timelock's `disposeQuarantined` (needs the transfer to succeed). A token that both blocks transfers from the vault (honeypot, blacklist) and whose feed is deprecated therefore stays in `_held` forever, and with it: `deposit` reverts for everyone, and `FeeWaterfall.pushBasketReserve` reverts, so the 40% basket share of every swap fee accumulates in the waterfall and never reaches the vault. Redemptions keep working (`strict = false`). The timelock's only workaround is to re-approve the token with a stand-in feed contract that returns a tiny positive price, which the methodology does not foresee.\n\nFix: in `IndexVault._nav`, treat a token that `registry.isQuarantined` and has no fresh price as zero value (mirroring RebalanceExecutor._nav), and/or let the timelock drop a quarantined, untransferable position from `_held` explicitly (`writeOff(token)`), with the holders' in-kind slice of it preserved.","line":320,"path":"src/IndexVault.sol","reproduction":"State: vault with Alice's 1,000,000 USDC invested in five members. Member T1 starts reverting transfers out of the vault (`blockedFrom = vault`) and its feed reverts; anyone calls registry.quarantineIfStale(T1).\nCalls: vault.deposit(100e6, Alice, 0) -> reverts PriceUnavailable(T1). usdc.mint(waterfall, 10_000e6); waterfall.pushBasketReserve(0) -> reverts PriceUnavailable(T1). Keeper executeTrade(T1, usdc, balance, ...) -> reverts PriceUnavailable. Timelock executor.disposeQuarantined(T1, balance, 1, venue, data) -> reverts 'transfers blocked'. vault.isHeld(T1) stays true; vault.nav() reports complete == false.\nExpected: a quarantined, failed asset is isolated and the rest of the system keeps working (brief: 'quarantine failed assets rather than bricking the basket').\nActual: deposits and the fee-to-basket flow are closed until governance installs a fake feed.\nRun: forge test --match-path test/scratch/LowLeads.t.sol --match-test honeypot -vv (reproduces the state).","severity":"low","snippet":"            else if (unpriced == address(0)) unpriced = token;","title":"A held token that becomes untransferable and loses its feed closes deposits and the basket-reserve push with no on-chain way out"},{"citation":"resolved","description":"`_authorize` refuses any sale whose fair output rounds to zero (`fair == 0 -> NothingToTrade`). An exit that leaves even 1 wei of an 18-decimal token behind (a venue that fills the exact amount minus dust, or a keeper choosing `balance - 1`) therefore leaves a position the keeper can never clear: 1 wei of a $100 token is 1e-16 USDC, so every later sell attempt reverts, and `endTrade` only drops a token whose balance is exactly zero. The slot stays occupied in `_held` (MAX_HELD = 10), the token stays in every NAV, redemption and holdings loop, and its feed must stay fresh or deposits close (see finding 4). Five such leftovers block the purchase of any further new member (`HeldListFull`). Only the timelock can clean up, by quarantining and disposing with `minOut = 1` through a venue willing to pay 1 unit for dust.\n\nFix: in `_authorize`, when `exit` is true and the whole balance is being sold, allow `fair == 0` with `oracleMinOut = 0` (there is nothing of value to protect), or let `endTrade` drop a held token whose `registry.convert(balance)` is zero.","line":264,"path":"src/RebalanceExecutor.sol","reproduction":"State: basket bought; guardian quarantines T0 (balance 1,960e18 in the vault).\nCalls: keeper executeTrade(T0, usdc, balance - 1, 0, venue, fair fill) -> (true, ...). tokens[0].balanceOf(vault) == 1; vault.isHeld(T0) == true; heldTokens().length == 5. Then executeTrade(T0, usdc, 1, 0, venue, swap(T0, usdc, 1, 1)) -> reverts NothingToTrade.\nExpected: an exited position is fully cleared or at least clearable.\nActual: the dust position is permanent on the keeper path and occupies a held slot.\nRun: forge test --match-path test/scratch/LowLeads.t.sol --match-test dustPosition -vv.","severity":"low","snippet":"        if (fair == 0) revert NothingToTrade();","title":"A position worth less than one reserve unit can never be sold through the keeper path and keeps its held slot"},{"citation":"resolved","description":"`redeem(shares, receiver, strict = true)` promises (NatSpec, README) that any token whose transfer fails reverts the redemption, so a holder can choose to wait rather than forfeit. But the payout amount comes from `_balanceOf`, which returns `(0, false)` when `balanceOf` reverts or returns short data, and the loop then hits `if (amount == 0) continue;` before any transfer is attempted. A strict redeemer burns all their shares and receives nothing of that token, with only a `RedemptionPayout` missing and no `RedemptionSkipped` event. The condition is temporary for tokens that pause `balanceOf` or revert while upgrading, so the holder had a real reason to wait. The forfeited slice stays with remaining holders.\n\nFix: in `redeem`, when `strict` is true and `_balanceOf(token)` reports `readable == false`, revert with `TransferFailed(token)` (or a dedicated `BalanceUnreadable(token)`), and emit `RedemptionSkipped` when `strict` is false.","line":163,"path":"src/IndexVault.sol","reproduction":"State: basket bought; T3's balanceOf starts reverting. Alice holds all shares.\nCall: vault.redeem(allShares, Alice, true).\nExpected: revert TransferFailed(T3) (strict mode).\nActual: succeeds; totalSupply == 0; Alice's T3 balance == 0; the vault still holds all of T3 and no RedemptionSkipped event was emitted.\nRun: forge test --match-path test/scratch/LowLeads.t.sol --match-test strictRedeem -vv.","severity":"low","snippet":"            if (amount == 0) continue;","title":"Strict redemption silently forfeits the slice of a token whose balanceOf reverts"},{"citation":"resolved","description":"`pushBasketReserve(minShares)` is permissionless and deposits the whole accrued basket bucket at the vault's feed NAV for shares owned by the timelock. The caller, not the treasury, picks the moment and the floor (`minShares` can be 0). The mirror image of finding 3: whenever the feeds are stale-high (market down by less than the deviation threshold, or a feed lagging right before an update that lowers its answer), a share holder calls `pushBasketReserve(0)` and the treasury overpays for its shares; the difference accrues to existing holders, including the caller. With a 10,000 USDC bucket and feeds 1.9% above market on a fully invested vault, the treasury receives shares worth about 9,814 USDC, a 186 USDC transfer per push, repeatable on every accumulation. It is bounded by the deviation threshold and the bucket size, which is why this is low.\n\nFix: restrict `pushBasketReserve` to a role (keeper or timelock) or compute `minShares` on-chain from `previewDeposit` with a tolerance set by the timelock, so a caller cannot choose a floor of zero.","line":129,"path":"src/FeeWaterfall.sol","reproduction":"State: fully invested vault, 10,000 USDC accrued in the basket bucket; every member feed answers $100 while the market is $98.10 (inside a 2% deviation threshold, feeds fresh).\nCall (any address): waterfall.pushBasketReserve(0).\nExpected: the treasury's deposit is priced by the treasury's own floor, or by an on-chain tolerance.\nActual: vault.deposit(10_000e6, timelock, 0) mints shares at the stale NAV; after the feeds update, previewRedeem(timelock shares) is worth about 9,814 USDC; the 186 USDC went to the other holders.","severity":"low","snippet":"        shares = vault.deposit(amount, address(admin), minShares);","title":"Anyone chooses when the treasury buys vault shares with the basket reserve, with minShares 0"},{"citation":"resolved","description":"README and METHODOLOGY say trading happens in a window 'that opens with a new epoch or once per RebalanceInterval'. `_requireWindow` instead sets `windowStart = block.timestamp` the first time a non-exit trade is attempted in a new epoch, whenever that is. A keeper can leave an activated basket untraded for days or weeks and then open a fresh two-day window at a time of its choosing (for example when its counterparty is positioned, or in a volatile hour), and the same applies to the periodic re-opening: `block.timestamp >= windowStart + RebalanceInterval` is measured from the keeper's previous window, not from a schedule. The published rule ('weekly execution') is therefore not what the contract enforces; it is a keeper-timed window of two days every seven-or-more days. Impact is timing freedom for a semi-trusted role rather than direct loss, so low.\n\nFix: have `EpochManager.activate` (or the executor on first sight of a new epoch) anchor `windowStart` to `activatedAt`, and re-open windows on a fixed schedule derived from it.","line":271,"path":"src/RebalanceExecutor.sol","reproduction":"State: epoch 1 activated at time T with material deficits for every member. No keeper trade for 30 days.\nCall at T + 30 days: keeper executeTrade(usdc, member, deficit, 0, venue, data).\nExpected (per README): the epoch's window closed at T + 2 days; the next window is on the interval schedule.\nActual: `current != windowEpoch` -> windowStart = T + 30 days; the trade and any trade in the following two days succeed (see test_tradesOutsideTheWindowWaitForTheNextInterval, which measures the window from the first trade, not from activation).","severity":"low","snippet":"        if (current != windowEpoch) {","title":"The rebalance window opens at the keeper's first trade, not at epoch activation, so the keeper picks when the two-day window runs"},{"citation":"resolved","description":"`IndexVault._balanceOf` caps a held token's `balanceOf` at 500,000 gas 'so one token cannot burn it all', and SECURITY.md lists 'burn gas' as a basket-token risk mitigated by gas-capped calls. `RebalanceExecutor._nav` makes the same call with no cap, and before consulting `isQuarantined`, so a member that starts burning gas (an upgradeable token after an upgrade or exploit) is called with 63/64 of the transaction's gas on every `executeTrade` and every `position()` read, quarantined or not. At a 30M gas limit the call burns 29,482,372 gas and the trade only completes because the last 1/64 (about 470k) happens to be enough; at lower limits every keeper trade reverts with out-of-gas. Every rebalance of every other member then costs the keeper about a full mainnet block of gas (at 20 gwei roughly 0.6 ETH per trade) until the timelock disposes of the token, and forever if the token also refuses transfers (finding 4). The vault side already handles this case correctly; the executor does not, so the stated 'one token cannot stop the basket' guarantee does not hold for rebalancing.\n\nFix: in `_nav`, skip quarantined tokens before calling them and cap the staticcall (`held[i].staticcall{gas: 500_000}(...)`) as the vault does; treat a failed read on a non-quarantined token as `UnpricedHolding` exactly as now.","line":310,"path":"src/RebalanceExecutor.sol","reproduction":"State: five-member basket bought; guardian quarantines T4 (so selling it is an exit) and T0. T0's balanceOf then loops until out of gas.\nCall: keeper executeTrade{gas: 30_000_000}(T4, reserve, balance, 0, venue, fair fill).\nExpected: a quarantined, unrelated token is skipped cheaply (as the vault does with a 500k cap).\nActual: the trace shows `Token::balanceOf(IndexVault)` consuming 29,482,372 gas (MemoryOOG) inside _nav before the quarantine check; the trade completes only on the remaining ~470k gas, and reverts at any gas limit where 1/64 is not enough for the swap. `executor.position(any)` behaves the same.\nRun: forge test --match-path test/scratch/GasBomb.t.sol -vvvv and read the balanceOf gas in the trace.","severity":"low","snippet":"            (bool ok, bytes memory ret) = held[i].staticcall(abi.encodeCall(IERC20.balanceOf, (address(vault))));","title":"RebalanceExecutor._nav gives every held token's balanceOf all remaining gas, so one hostile member makes every keeper trade cost a full block"}],"hash":"a01c467ca0362ba4176c9d3dfa056a886a6e8f31467420c96cfb3c942ab76814","nodeId":"4f000c45-6ee2-4237-84c9-796399ac0390","outcome":"completed","summary":"The review is done. No tracked file was changed; `.imd-findings.json` and `test/scratch/` are the only additions, and both are already excluded by the repository's git config.\n\n## What I found\n\nI audited the economics (Economic Security, Invariant and Flow Gap lenses) across the hook, waterfall, vault and executor. The existing 215 tests pass. Nine defects are recorded in `.imd-findings.json`, three with Foundry proofs that fail on the current code:\n\n**Medium, with proofs**\n1. **FeeHook charges the quote-specified fee on the requested amount, not the filled amount.** In v4 the trader's delta is `swapDelta - hookDelta`, so a partial fill keeps the full-request fee. Against the real PoolManager: a sell-exact-output of 1,000 quote filled with 0.99 quote cost 7.5 quote in fees, leaving the trader negative in both currencies. An exact-input buy that exhausted the range paid 7,500 on 350,715 traded.\n2. **A keeper can liquidate the entire basket in one block.** Three self-made failing swaps per member auto-quarantine it, a quarantined token has target zero, and target-zero sales skip the delta, drift and window rules. The proof moves a 1.15M USDC vault to all reserve and costs holders 9,800 USDC.\n3. **Same-block deposit at feed NAV plus in-kind redemption captures feed lag from existing holders.** With every member up 1.9% and feeds inside their deviation threshold, the attacker turns 1,000,000 USDC into 1,009,310 and Alice loses the same amount. The README names the latency, but not that the arbitrage is atomic and flash-loanable.\n\n**Low (reproduced, no proof)**\n4. An untransferable held token with a dead feed closes deposits and the basket-reserve push permanently.\n5. Dust positions below one reserve unit can never be sold via the keeper path and keep a held slot.\n6. Strict redemption silently forfeits the slice of a token whose `balanceOf` reverts.\n7. Anyone chooses when the treasury buys shares via `pushBasketReserve`, with `minShares` zero.\n8. The rebalance window opens at the keeper's first trade, not at epoch activation, contrary to the README.\n9. `RebalanceExecutor._nav` calls `balanceOf` uncapped and before the quarantine check; a gas-burning member costs ~29.5M gas per trade.\n\n## Coverage\n\nAll 57 listed entry points have a row (54 holds, 7 finding, 0 unreached), plus four invariant rows. The timelock and launch-token entry points were traced lightly since they sit outside my area. Not covered: mainnet-fork behaviour with live tokens, feeds and routers, and long fuzz campaigns.","treeHash":null,"usage":{"cachedInputTokens":8062200,"inputTokens":1154,"model":"claude-fable-5-1","outputTokens":120950,"runtime":"claude","turns":71,"wallClockMs":1766364}},{"artifacts":[],"attempt":1,"bundleHash":"3d6dec5bafefbab99f4dd64b41fdb2b4edfcaa482e7823b3ced5ba6bd012f584","device":"c7b1db4d25939bd5","findings":[],"hash":"b8b25c0f4f6590d018965896349a81ad11921a3c33f18874bf881568ae096316","nodeId":"9365e8f1-318d-4720-be78-e410ebc31c87","outcome":"completed","summary":"Updated only `launch.json`, clarifying quarantine and redemption behavior while preserving deployment values.\n\nValidation passed: supplied schema, constructor ABIs, `forge build`, and all 256 tests, including the protected checks.","treeHash":"8ba13686af83ac63e6db21d3e51780398c13fbc2","usage":{"cachedInputTokens":399744,"inputTokens":60030,"model":"gpt-6-astra","outputTokens":4726,"runtime":"codex","turns":4,"wallClockMs":141852}},{"artifacts":[],"attempt":1,"bundleHash":"f7e4f5b9833956051c106ef31ad5777e57b9d98e9356bb793c99d1976d3884be","device":"b414b10f97bca557","findings":[],"hash":"b9b7d181fa84d3465d1b63b472d342781c50da242fd41704fa56f14368f94e87","nodeId":"abc6922f-88dc-4772-a2e0-5d80a0d81188","outcome":"completed","summary":"The IMD Index build is in the working tree and passes `forge build`, `forge test` (215 tests, 0 failed) and `forge fmt --check` on solc 0.8.26. I did not commit, because the rules put `.git/` off limits.\n\n## What was built\n\n- **`LaunchToken`** — IMD Index / IMDEX, fixed 1,000,000,000 supply, 18 decimals, no admin surface.\n- **`TimelockedAdmin`** — timelock plus role table. One address holds at most one role and the admin wallet holds none.\n- **`AssetRegistry`** — token allowlist, price feeds, weight caps, approved routers and every bounded parameter.\n- **`EpochManager`** — verifies quorum-signed EIP-712 proposals against the hard rules, holds them for a delay, re-checks at activation, and anchors daily report hashes.\n- **`IndexVault`** — `vIMDEX` shares, deposits at oracle NAV, in-kind redemptions that no pause, oracle or keeper can block.\n- **`RebalanceExecutor`** — the only trading path: delta-only, reserve buffer, oracle price floor, router allowlist, weekly window, quarantine after repeated failures.\n- **`FeeWaterfall`** — the 40/25/20/10/5 split on-chain, changed only through the timelock.\n- **`FeeHook` and `FeeHookDeployer`** — the v4 hook collects the fee in the quote currency on all four swap shapes and never trades the basket.\n\nDocs are `README.md`, `docs/METHODOLOGY.md`, `docs/SECURITY.md` and `docs/PROPOSAL.md` (the milestone proposal the brief asked for).\n\n## Where this differs from the brief\n\n- **IMDEX is not the vault share.** The launch token must be fixed-supply, so index shares are a second token, `vIMDEX`.\n- **The fee hook cannot sit on the launch pool.** The launch pool gets the network's own guard and fee, so the hook works on a second v4 pool for the same pair. Moving liquidity there is your decision.\n- **The hook is deployed after launch.** A v4 hook needs a mined address, so the launch deploys `FeeHookDeployer` and anyone then calls `deploy(salt)`.\n- **The 25% LP share never reaches the waterfall.** It is applied as the pool's LP fee; the hook collects the other 75%.\n- **No role exists at launch.** Only `$owner` is available to a constructor, so guardian, signers, keepers and executor are assigned through the timelock afterwards. Until then deposits are closed and nothing can trade.\n- **CoW Swap is not integrated.** Execution is route-agnostic with an on-chain oracle floor; a CoW adapter is milestone M5.\n- **No mainnet-fork tests.** The verifier has no network; they are listed as required before real funds.\n\n## Checks\n\n- The hook tests run inside the real Uniswap v4 `PoolManager`, with native ETH and with an ERC-20 quote on either side of the pair.\n- I ran the two protected launch-floor tests locally against the built artifacts with the seven application contracts and mainnet literals; all nine pass.\n- The invariant suite found one real defect, now fixed and covered: a sale too small to buy one unit of the target token had a price floor of zero.\n- The five mainnet addresses in the README (PoolManager, USDC, WETH, USDC/USD and ETH/USD feeds) returned code and the expected names from a public RPC today. They are constructor arguments, not constants.\n- Slither, Mythril and long fuzz campaigns were not run. Passing tests are not an audit.\n\n## Open decisions for you\n\n- Reserve asset, USDC or WETH; it is fixed at launch.\n- The guardian, signer, keeper and fee-recipient wallets, and the quorum (two recommended, one accepted).\n- The named lead agent and human technical owner; I left both unnamed rather than invent them.\n- Starting numbers I chose where the brief gave none, all changeable through the timelock:\n  - minimums of $250M market cap, $5M liquidity and $5M daily volume;\n  - 1% total swap fee, 1% maximum slippage, 2% reserve buffer, 2.5% drift threshold;\n  - 6-hour proposal delay and 2-day timelock;\n  - rank-buffer thresholds in the methodology.","treeHash":"38745421de6f0d17282976125b9c34cb7ea2e40e","usage":{"cachedInputTokens":9925042,"inputTokens":90,"model":"claude-fable-5-1","outputTokens":244537,"runtime":"claude","turns":46,"wallClockMs":2937560}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a4c81f495eb81dd0","findings":[{"citation":"resolved","description":"Merged from audit_permissions and audit_flow (same root cause). `cancel` lets the guardian delete any scheduled operation with no exception for operations that limit the guardian itself. `setGuardian` (line 179, onlySelf) and the timelock path of `unpause` (line 228) are reachable only through schedule -> minDelay -> execute, and the operation id is published in the `Scheduled` event at least minDelay before it can run. A compromised or hostile guardian watches that event and cancels every replacement, every unpause, every setSigner/setKeeper/setExecutor re-grant, while using its immediate powers (`pause`, `revokeSigner`, `revokeKeeper`, `cancelPending`) to stop deposits, proposals and trading. docs/SECURITY.md bounds the guardian with \"the timelock can replace the guardian and lift a pause\"; neither holds. Consequences: deposits closed (vault checks admin.paused()), no publish/activate/anchorReport, no keeper trades, and FeeWaterfall.pushBasketReserve reverts because vault.deposit is paused, so the 40% basket share of every swap fee is stranded in the waterfall forever (the Basket bucket cannot be claimed and the reserve cannot be rescued). Only in-kind redemption survives. The brief requires that no single wallet control every function; this one key decides whether anything but redeem ever runs again, with no recovery path for the admin. Minimal fix that keeps the guardian veto over everything that moves funds or widens access: refuse a guardian cancel (admin cancel stays) of operations whose target is address(this) and whose selector is setGuardian or unpause (record target/selector at schedule time, or make cancel take target/value/data/salt and recompute the id); alternatively let those two operations execute after a longer fixed delay without veto. The existing suite has no test where the guardian cancels a self-targeted operation.","line":146,"path":"src/TimelockedAdmin.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\n\n/// @dev Finding: the guardian can cancel *any* scheduled operation, including the timelock's own\n/// `setGuardian` and `unpause`. SECURITY.md states the guardian is limited because \"the timelock can\n/// replace the guardian and lift a pause\". It cannot: a guardian that watches `Scheduled` events\n/// cancels its own replacement before `execute`, and the pause it set can never be lifted.\n/// This test fails on the current code (execute reverts NotScheduled after the guardian's cancel)\n/// and passes once the guardian's veto no longer reaches those two operations.\ncontract GuardianVetoTest is Test {\n    address internal constant ADMIN = address(0xA11CE);\n    address internal constant GUARDIAN = address(0x6A4D);\n    address internal constant NEW_GUARDIAN = address(0x6A4E);\n    uint256 internal constant DELAY = 1 days;\n\n    TimelockedAdmin internal tl;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        tl = new TimelockedAdmin(ADMIN, DELAY);\n        _govNow(abi.encodeCall(tl.setGuardian, (GUARDIAN)), bytes32(uint256(1)));\n        assertEq(tl.guardian(), GUARDIAN);\n    }\n\n    function test_timelockCanReplaceGuardianDespiteVeto() public {\n        bytes memory data = abi.encodeCall(tl.setGuardian, (NEW_GUARDIAN));\n        bytes32 salt = bytes32(uint256(2));\n        vm.prank(ADMIN);\n        bytes32 id = tl.schedule(address(tl), 0, data, salt, DELAY);\n\n        // The guardian sees the Scheduled event and vetoes its own replacement.\n        vm.prank(GUARDIAN);\n        try tl.cancel(id) {} catch {}\n\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n        assertEq(tl.guardian(), NEW_GUARDIAN, \"timelock could not replace the guardian\");\n    }\n\n    function test_timelockCanLiftGuardianPauseDespiteVeto() public {\n        vm.prank(GUARDIAN);\n        tl.pause();\n        assertTrue(tl.paused());\n\n        bytes memory data = abi.encodeCall(tl.unpause, ());\n        bytes32 salt = bytes32(uint256(3));\n        vm.prank(ADMIN);\n        bytes32 id = tl.schedule(address(tl), 0, data, salt, DELAY);\n\n        vm.prank(GUARDIAN);\n        try tl.cancel(id) {} catch {}\n\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n        assertFalse(tl.paused(), \"timelock could not lift the guardian's pause\");\n    }\n\n    function _govNow(bytes memory data, bytes32 salt) private {\n        vm.prank(ADMIN);\n        tl.schedule(address(tl), 0, data, salt, DELAY);\n        skip(DELAY);\n        vm.prank(ADMIN);\n        tl.execute(address(tl), 0, data, salt);\n    }\n}","reproduction":"State: TimelockedAdmin(admin=A, minDelay=1 day); guardian G granted through the timelock. (1) G calls pause() -> paused=true. (2) A calls schedule(tl, 0, abi.encodeCall(setGuardian,(G2)), salt, 1 day) -> Scheduled(id). (3) G calls cancel(id) -> succeeds, readyAt[id]=0. (4) After 1 day A calls execute(tl, 0, same data, salt) -> reverts NotScheduled(). Same sequence with data = abi.encodeCall(unpause,()) -> NotScheduled(). Expected per docs/SECURITY.md: guardian replaced / pause lifted after the delay. Actual: guardian stays G and paused stays true for as long as G keeps cancelling (one cheap tx per attempt). Verified: forge test --match-path test/scratch/Proof_8617dee17d28.t.sol -> both tests FAIL with NotScheduled() on the current code.","severity":"high","snippet":"    function cancel(bytes32 id) external {\n        if (msg.sender != admin && msg.sender != guardian) revert NotAuthorized();","title":"Guardian can veto its own replacement and the timelock unpause, so a hostile guardian freezes governance permanently"},{"citation":"resolved","description":"Merged from audit_economics (medium) and audit_math (low). When the quote currency is the specified side (exact-input buy of IMDEX, exact-output sale of IMDEX), `beforeSwap` computes the fee from `params.amountSpecified` (line 138-139) and takes it from the PoolManager before the pool runs. In v4 the hook delta is simply subtracted from the trader delta after the swap (lib/v4-core/src/libraries/Hooks.sol afterSwap: `swapDelta = swapDelta - hookDelta`), so if the pool fills only part of the request (sqrtPriceLimitX96 reached, or the active range exhausted, which is what the default limits of every router produce) the full-request fee stays charged. `afterSwap` does this correctly for the unspecified side because it reads `swapDelta`. Measured on the real PoolManager with 1e24 liquidity in ticks -6000..6000 and the 1% starting fee: an exact-output sale for 1,000 quote with a tight limit delivers ~1 quote, the waterfall takes 7.5 quote and the seller ends the swap with -6.5 quote AND -1.0025 IMDEX (paid both currencies, received nothing; effective fee 750%); an exact-input buy with 1,000,000 quote absorbs 350,715 and pays 7,500 fee instead of at most 2,687. The overcharge is paid to the waterfall buckets, i.e. funds to the wrong party, and a sandwicher who pushes the price to a victim limit makes the victim pay the full fee on near-zero execution. The suite tests only full fills. Fix preserving the design: charge the quote-specified fee on the filled amount. Either (a) in afterSwap compare |swapDelta.specified| with the requested amount and revert on a partial fill, or (b) take a provisional fee in beforeSwap and refund the difference as a negative hookDeltaUnspecified in afterSwap (documenting that the refund arrives in the other currency), or (c) move the quote-specified fee entirely to afterSwap based on amountSpecified minus what the pool left unfilled.","line":140,"path":"src/FeeHook.sol","reproduction":"Hooked pool initialised at 1:1 with 1e24 liquidity between ticks -6000 and 6000, swapFee 1% (0.25% LP, 0.75% hook). Trader holds 100,000 IMDEX and 2,000,000 quote. (1) swap(key, {zeroForOne: !quoteIsCurrency0, amountSpecified: +1000e18, sqrtPriceLimitX96: spot +/- spot/1e6}) through an unlock router. Expected: fee <= 0.75% of the quote actually delivered, or a refusal. Actual: waterfall +7.5e18 quote, trader quote balance -6.5e18, trader IMDEX -1.0025e18. (2) swap(key, {zeroForOne: quoteIsCurrency0, amountSpecified: -1_000_000e18, default limit}). Expected: fee <= 0.75% of the ~358,215 quote actually paid (<= 2,687). Actual: fee 7,500e18. Verified: forge test --match-path test/scratch/HookPartialFill.t.sol -vv -> both tests FAIL on the current code with those numbers.","severity":"medium","snippet":"            delta = toBeforeSwapDelta(_collect(sender, specified, q, params.zeroForOne), 0);","title":"FeeHook charges the quote-specified fee on the requested amount, not on what the pool filled; a partial fill pays fee on volume that never traded"},{"citation":"resolved","description":"Merged from write_foundry_tests (medium), audit_math (low) and audit_economics (low). NatSpec and README promise that with strict=true \"any token whose transfer fails reverts the redemption\" so a holder can wait instead of forfeiting. But the `readable` flag returned by `_balanceOf` is discarded on line 161: a token whose balanceOf reverts, returns short data or exceeds the 500k gas cap reads as balance 0, `amount` becomes 0 and line 163 `continue`s before any transfer is attempted, so the strict branch on line 167 is never reached. The shares are burned, the redeemer receives nothing of that token, no RedemptionSkipped event is emitted, and the forfeited slice accrues to the remaining holders. balanceOf reverting is often temporary (paused or upgrading token), which is exactly when a holder would choose strict mode. Fix: when strict is true and readable is false, revert TransferFailed(token) (or a dedicated BalanceUnreadable(token)); when strict is false emit RedemptionSkipped. The suite tests unreadable balances only for deposits and non-strict redemption.","line":161,"path":"src/IndexVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {IndexVault} from \"src/IndexVault.sol\";\n\ncontract RedemptionProofToken {\n    mapping(address => uint256) private balances;\n    mapping(address => mapping(address => uint256)) public allowance;\n    bool public broken;\n    function decimals() external pure returns (uint8) { return 6; }\n    function mint(address to, uint256 amount) external { balances[to] += amount; }\n    function breakBalance() external { broken = true; }\n    function balanceOf(address who) external view returns (uint256) {\n        require(!broken, \"balance temporarily unavailable\"); return balances[who];\n    }\n    function approve(address to, uint256 amount) external returns (bool) { allowance[msg.sender][to] = amount; return true; }\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balances[msg.sender] -= amount; balances[to] += amount; return true;\n    }\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount; balances[from] -= amount; balances[to] += amount; return true;\n    }\n}\ncontract RedemptionProofExecutor {\n    function track(IndexVault vault, address token) external {\n        vault.beginTrade(vault.asset(), 0); vault.endTrade(token);\n    }\n}\ncontract StrictRedemptionProof is Test {\n    TimelockedAdmin admin;\n    function configure(address target, bytes memory data) internal {\n        bytes32 salt = keccak256(data);\n        admin.schedule(target, 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        admin.execute(target, 0, data, salt);\n    }\n    function test_strictRedemptionMustPreserveClaimOnUnreadableAsset() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(address(this), 1 days);\n        RedemptionProofToken reserve = new RedemptionProofToken();\n        RedemptionProofToken held = new RedemptionProofToken();\n        AssetRegistry registry = new AssetRegistry(address(admin), address(reserve), 6);\n        IndexVault vault = new IndexVault(address(admin), address(registry), address(reserve), 6, 1_000_000e6);\n        RedemptionProofExecutor executor = new RedemptionProofExecutor();\n        configure(address(admin), abi.encodeCall(admin.setGuardian, (address(0xBEEF))));\n        configure(address(admin), abi.encodeCall(admin.setExecutor, (address(executor))));\n        reserve.mint(address(this), 1000e6);\n        reserve.approve(address(vault), 1000e6);\n        uint256 shares = vault.deposit(1000e6, address(this), 0);\n        held.mint(address(vault), 1000e6);\n        executor.track(vault, address(held));\n        assertTrue(vault.isHeld(address(held)));\n        held.breakBalance();\n        (bool ok,) = address(vault).call(abi.encodeCall(vault.redeem, (shares, address(this), true)));\n        assertFalse(ok, \"strict redemption burned shares without paying the held asset\");\n        assertEq(vault.balanceOf(address(this)), shares, \"claim must survive a strict failure\");\n    }\n}","reproduction":"State: vault with 1,000 units of a 6-decimal reserve deposited by Alice (sole holder) and a second token tracked in _held with a non-zero vault balance (the proof uses an executor stub that calls beginTrade(asset,0)/endTrade(token)). The held token then makes balanceOf revert. Alice calls vault.redeem(allShares, Alice, true). Expected: revert TransferFailed(token) and shares preserved. Actual: the call succeeds, totalSupply becomes 0, Alice receives only the reserve and the held token stays in the vault with no shares outstanding against it. Verified: forge test --match-path test/scratch/Proof_85479074f3b7.t.sol -> FAIL \"strict redemption burned shares without paying the held asset\". The same outcome occurs in the five-token fixture with tokens[3].setBalanceReverts(true).","severity":"medium","snippet":"            (uint256 balance,) = _balanceOf(token);\n            amount = Math.mulDiv(balance, shares, supply);\n            if (amount == 0) continue;","title":"redeem(strict=true) silently forfeits the slice of a held token whose balanceOf is unreadable"},{"citation":"resolved","description":"From write_foundry_tests. README states the hard rules are \"enforced by EpochManager when a proposal is published and again when it is activated\", including \"snapshot ... not older than MaxSnapshotAge (1 day)\". `activate` re-checks only expiry, interval, signer-set version, methodology, eligibility and weight caps (lines 182-198); snapshot age is checked solely in `_checkTiming` at publish. With the default 1-day MaxSnapshotAge, a signer-chosen expiry of up to 7 days and the TooSoonSinceLastEpoch rule that can hold a proposal for days, activation routinely commits a basket whose snapshot is several days old, and the brief's hard exclusion of stale data is not enforced at the moment that matters. Fix: in activate(), revert StaleSnapshot when block.timestamp - q.snapshotTime > registry.param(Param.MaxSnapshotAge); alternatively cap p.expiry at p.snapshotTime + MaxSnapshotAge in _checkTiming (the attached proof assumes the first form).","line":183,"path":"src/EpochManager.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {EpochManager} from \"src/EpochManager.sol\";\n\ncontract FreshProofFeed {\n    function decimals() external pure returns (uint8) { return 8; }\n    function latestRoundData() external view returns (uint80,int256,uint256,uint256,uint80) {\n        return (1, 1e8, block.timestamp, block.timestamp, 1);\n    }\n}\ncontract ProofAsset { function decimals() external pure returns (uint8) { return 18; } }\ncontract StaleActivationProof is Test {\n    TimelockedAdmin admin;\n    AssetRegistry registry;\n    EpochManager epochs;\n    function configure(address target, bytes memory data) internal {\n        bytes32 salt = keccak256(data);\n        admin.schedule(target, 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        admin.execute(target, 0, data, salt);\n    }\n    function test_expiryMustNotOverrideSnapshotFreshness() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(address(this), 1 days);\n        registry = new AssetRegistry(address(admin), address(0xCA01), 6);\n        epochs = new EpochManager(address(admin), address(registry));\n        ProofAsset token = new ProofAsset();\n        FreshProofFeed feed = new FreshProofFeed();\n        configure(address(admin), abi.encodeCall(admin.setSigner, (vm.addr(123), true)));\n        configure(address(admin), abi.encodeCall(admin.setQuorum, (1)));\n        configure(address(registry), abi.encodeCall(registry.approveToken,\n            (address(token), address(feed), 1 hours, 2000, uint40(block.timestamp - 40 days), keccak256(\"review\"))));\n        EpochManager.Proposal memory p;\n        p.epoch = 1;\n        p.snapshotTime = uint64(block.timestamp);\n        p.expiry = uint64(block.timestamp + 3 days);\n        p.methodologyVersion = 1;\n        p.signerSetVersion = admin.signerSetVersion();\n        p.dataHash = keccak256(\"daily research\");\n        p.tokens = new address[](1); p.tokens[0] = address(token);\n        p.weightsBps = new uint16[](1); p.weightsBps[0] = 2000;\n        p.marketCapsUsd = new uint256[](1); p.marketCapsUsd[0] = 1_000_000_000;\n        p.liquidityUsd = new uint256[](1); p.liquidityUsd[0] = 50_000_000;\n        p.volumesUsd = new uint256[](1); p.volumesUsd[0] = 50_000_000;\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(123, epochs.hashProposal(p));\n        bytes[] memory sigs = new bytes[](1); sigs[0] = abi.encodePacked(r,s,v);\n        epochs.publish(p, sigs);\n        vm.warp(block.timestamp + 25 hours);\n        assertTrue(registry.isEligible(address(token)), \"price remains fresh\");\n        assertLt(block.timestamp, p.expiry, \"proposal has not expired\");\n        vm.expectRevert(EpochManager.StaleSnapshot.selector);\n        epochs.activate();\n    }\n}","reproduction":"State: real TimelockedAdmin, AssetRegistry, EpochManager; one signer, quorum 1; one approved token aged 40 days whose feed always returns a fresh price. Publish a valid epoch-1 proposal at time T with snapshotTime = T and expiry = T + 3 days (accepted: snapshot age 0 <= 1 day). Warp to T + 25 hours; isEligible(token) is still true and the proposal has not expired. Call activate(). Expected: revert StaleSnapshot (snapshot 25h old > MaxSnapshotAge 24h). Actual: activation succeeds and epoch() == 1. Verified: forge test --match-path test/scratch/Proof_9da0875e4c3a.t.sol -> FAIL \"next call did not revert as expected\".","severity":"medium","snippet":"        if (block.timestamp > q.expiry) revert ProposalExpired();","title":"activate() does not re-check MaxSnapshotAge, so a basket is committed on research older than the published stale-data rule allows"},{"citation":"resolved","description":"Merged from audit_economics (medium) and audit_flow (info). All three keeper limits (delta-only, drift threshold, rebalance window) hang on `exit = target == 0`, and `_target` returns 0 for any quarantined token. The executor itself quarantines after FailureThreshold (3) consecutive caught failures (lines 124-127), a failure is anything the keeper's router calldata makes revert (router.fail()), and failures are counted per call with no block or time spacing. So a keeper submits three failing calls per member in one block, every member becomes quarantined, and the exit branch then allows sellAmount <= balance with no window, drift or active-basket requirement; each sale only has to clear the oracle floor less MaxSlippageBps (1%) through a venue the keeper picks. The only precondition for the failing trades is an authorised buy (material deficit inside the window), which a 15% deposit or any new epoch provides. SECURITY.md bounds the keeper to \"up to MaxSlippageBps of each allowed trade\"; here the keeper enlarges \"allowed\" to the entire basket, converts the index to cash, and nothing can be bought back until the timelock releases each token (>= minDelay each) and a window opens. Two unprivileged amplifiers share the root cause: (a) three sandwiched keeper trades (InsufficientOutput lands in the catch) quarantine an honest member; (b) a quarantined token in a pending proposal makes activate() revert NotEligible, so the keeper can also veto epoch transitions. Fix preserving the design: an executor-set quarantine should not by itself grant exit rights (keep an autoQuarantined flag that blocks buys but leaves the basket target intact until the guardian or timelock confirms), and/or count at most one failure per block or window so three failures cannot be manufactured atomically.","line":244,"path":"src/RebalanceExecutor.sol","reproduction":"State: Alice deposits 1,000,000 USDC; epoch 1 activates five tokens at 2000 bps; keeper buys each to target (196,000) at oracle price. Keeper deposits 150,000 USDC so each deficit (29,400) exceeds the 2.5% drift threshold (28,750). Same block, all by KEEPER: for each member 3x executeTrade(usdc, member, 1000e6, 0, router, abi.encodeCall(router.fail,())) -> each returns (false,0); after the third registry.isQuarantined(member) == true. Then for each member executeTrade(member, usdc, vault balance, 0, router, fill at 99.01% of oracle) -> (true, ...). Expected: positions at target cannot be sold at all, and never beyond the delta. Actual: vault.heldTokens().length == 0, usdc in vault 1,140,298, NAV fell from 1,150,000 to 1,140,298 (9,702 USDC to the keeper's venue). Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_H -vv passes with those logs on the current code.","severity":"medium","snippet":"            exit = target == 0;\n            if (exit) {\n                if (sellAmount > balance) revert ExceedsDelta(sellAmount, balance);","title":"A keeper can quarantine every member with its own failing swaps and liquidate the whole basket in one block at the slippage floor"},{"citation":"resolved","description":"From audit_economics. Deposits mint at navBefore, the sum of held balances at each feed's last answer; redemptions pay a pro-rata slice in kind and need no price. Nothing links the two in time: deposit and redeem are separate nonReentrant calls that run back to back in one transaction, there is no cooldown and DepositFeeBps defaults to 0. Chainlink pushes a new answer only when the price moves by the deviation threshold (0.5%-2% for ETH-ecosystem USD feeds), so whenever the market is up by less than that, NAV is stale-low and deposit -> redeem -> sell is a riskless transfer from existing holders (including the treasury's fee-funded shares) to the caller, with profit scaling up to half the lag at deposit = NAV. README names feed latency as a limitation mitigated by the cap and the optional fee; the defect reported is that the arbitrage is atomic, needs no capital at risk, and the maximum fee (1%) does not cover a 2% threshold while the default (0) covers nothing. Fix (a design decision the requester must make): record the deposit block/timestamp per receiver and refuse redeem/transfer of shares minted in the same block or within a short minimum holding period; and/or set a non-zero default DepositFeeBps at or above the largest configured feed deviation. The suite's round-trip fuzz asserts no gain only at an unchanged price.","line":134,"path":"src/IndexVault.sol","reproduction":"State: Alice deposited 1,000,000 USDC; five tokens bought at $100 each (feeds 100e8, fresh). True market price is $101.90 but no feed crossed its 2% deviation. Attacker in one transaction: usdc.approve(vault, 1e12); shares = vault.deposit(1e12, attacker, 0); vault.redeem(shares, attacker, true). Expected: a deposit immediately redeemed returns at most what was deposited. Actual: attacker holds 510,000 USDC plus ~980.96 of each token, worth 1,009,310 USDC at the true price (+9,310); Alice's previewRedeem value at the true price drops from 1,018,620 to 1,009,310. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_I -vv.","severity":"medium","snippet":"        shares = Math.mulDiv(received - fee, totalSupply() + VIRTUAL_SHARES, navBefore + 1);","title":"Same-block deposit at feed NAV and in-kind redemption lets anyone capture the feeds' lag from existing holders, risk-free and flash-loanable"},{"citation":"resolved","description":"From audit_math. `_nav` values every held token at its fresh feed price and never consults registry.isQuarantined, so a position the system itself has classified as failed keeps its full value in the NAV that prices deposits. Redemptions are in kind: a token that refuses transfers from the vault is skipped (strict=false, slice stays in the vault) or reverts (strict=true). A depositor entering after the quarantine is therefore charged for the quarantined token's share of NAV and can never take it out; the difference goes to earlier holders. The README doctrine \"a deposit that cannot be priced fairly is refused rather than guessed\" is applied to the unpriced case (line 125) but not here. Unprivileged amplifier: anyone can call FeeWaterfall.pushBasketReserve(0), depositing the treasury's reserve at the inflated NAV. The executor cannot clear the position either: beginTrade's safeTransfer reverts for a transfer-blocked token so disposeQuarantined fails, and the mispricing is permanent. Note the related design gap: there is no write-off path for such a position (see the low finding on a quarantined token with a dead feed). Fix preserving the design: in deposit() also revert while any held token with non-zero balance is quarantined (treat like unpriced), or give the timelock an explicit write-off that drops the position from NAV while preserving holders' in-kind slice.","line":318,"path":"src/IndexVault.sol","reproduction":"State: Alice deposited 1,000,000 USDC; basket of one token T1 (18 dec, $100, weight 2000) activated and bought to its 196,000 USDC target. T1 then blocks transfers from the vault (honeypot); guardian calls registry.quarantine(T1); T1's feed stays fresh. vault.nav() returns 1,000,000e6 with complete == true. Bob calls vault.deposit(100_000e6, BOB, 0) and immediately vault.redeem(shares, BOB, false). Expected: revert on deposit (held token quarantined) or a round trip worth ~100,000 USDC. Actual: Bob receives 82,181.82 USDC and nothing of T1 (transfer fails, skipped): a 17,818 USDC loss in one block credited to Alice. disposeQuarantined(T1, 1e18, 1, router, ...) by the timelock reverts \"transfers blocked\". Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_E -vv.","severity":"medium","snippet":"            if (ok) (tokenValue, ok) = registry.convert(token, balance, asset);","title":"Deposit NAV values a quarantined, untransferable held token at full oracle price; later depositors pay for value they can never receive"},{"citation":"resolved","description":"Merged from audit_permissions (medium) and audit_economics (low). IndexVault._balanceOf (lines 326-330) reads balanceOf with {gas: TRANSFER_GAS} precisely so that a token which burns gas (a listed basket-token threat in docs/SECURITY.md, \"limited by gas-capped calls\") cannot block redemptions. RebalanceExecutor._nav performs the same read with a plain staticcall that forwards 63/64 of all remaining gas, and does so before consulting isQuarantined. _nav runs at the top of _authorize for every executeTrade, including trades of unrelated members. A held token whose balanceOf consumes everything it is given (an upgraded or exploited member) leaves 1/64 of the budget, which is not enough for the swap plus IndexVault.endTrade (which spends up to 500k on the same token again), so every keeper trade of every member reverts out of gas. Quarantining the token does not help because the read precedes the quarantine check; disposeQuarantined bypasses _nav but afterwards the vault reports readable=false so the token is never dropped from _held and _nav keeps burning on it. The executor can never trade again until a fixed executor is deployed and wired through the timelock. Fix: cap the staticcall in _nav (and the balanceOf in _valueOf, line 299) with the same 500k gas and skip quarantined tokens before calling them, treating a failed read on a non-quarantined token as UnpricedHolding exactly as now.","line":310,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket {T0 20%, BURN 20%} bought to target on a 1,000,000 USDC vault; BURN then switches its balanceOf to `assembly { invalid() }`; guardian calls registry.quarantine(BURN). T0's feed doubles so T0 is far over target. Keeper calls executor.executeTrade{gas: 30_000_000}(T0, USDC, 100e18, 0, router, router.swap(T0, USDC, 100e18, fair)). Expected: success=true, T0 sold toward target. Actual: the call reverts (out of gas) at 30M and at 10M gas; the same state lets vault.redeem{gas: 3_000_000}(1e12, ALICE, false) succeed, showing the vault's capped read survives. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_B -vv (logs \"executeTrade with 30M gas: reverted\", \"executeTrade with 10M gas: reverted\").","severity":"medium","snippet":"            (bool ok, bytes memory ret) = held[i].staticcall(abi.encodeCall(IERC20.balanceOf, (address(vault))));","title":"RebalanceExecutor._nav reads held-token balances with no gas cap, so one gas-burning basket token disables every keeper trade (the vault caps the same read)"},{"citation":"resolved","description":"From audit_economics. IndexVault._nav treats every held token with a non-zero balance and no fresh price as unpriced and deposit() reverts PriceUnavailable for it, even when the token is quarantined; RebalanceExecutor._nav deliberately counts a quarantined unpriced token as zero, so the two disagree. A token is dropped from _held only by endTrade when its balance reads zero, and the only ways to empty it are a keeper sale (needs a price) or disposeQuarantined (needs the transfer to succeed). A token that both blocks transfers from the vault and whose feed is dead therefore stays in _held forever: deposit reverts for everyone and FeeWaterfall.pushBasketReserve reverts, so the 40% basket share of every swap fee accumulates in the waterfall and never reaches the vault. Redemptions keep working. The only workaround is to re-approve the token with a stand-in feed, which then re-introduces the mispricing of the previous finding. Fix: in IndexVault._nav treat a quarantined token with no fresh price as zero value (mirroring the executor), and/or give the timelock an explicit writeOff(token) that drops a quarantined untransferable position from _held while holders keep their in-kind slice.","line":320,"path":"src/IndexVault.sol","reproduction":"State: Alice's 1,000,000 USDC invested in five members. T1 starts reverting transfers out of the vault and its feed reverts; anyone calls registry.quarantineIfStale(T1). vault.deposit(100e6, ALICE, 0) -> reverts PriceUnavailable(T1). usdc.mint(waterfall, 10_000e6); waterfall.pushBasketReserve(0) -> reverts PriceUnavailable(T1). Timelock executor.disposeQuarantined(T1, balance, 1, router, ...) -> reverts \"transfers blocked\". vault.isHeld(T1) stays true. Expected per brief: a failed asset is quarantined rather than bricking the basket. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_J -vv.","severity":"low","snippet":"            else if (unpriced == address(0)) unpriced = token;","title":"A held token that is untransferable and has lost its feed closes deposits and the basket-reserve push with no on-chain way out"},{"citation":"resolved","description":"From audit_flow. `_checkBasket` counts additions only when the active basket has at least one token (line 346 `if (hadBasket && !_isActiveMember(token)) ++additions;`). Nothing requires a proposal to contain a token: `n > MAX_ASSETS` is the only length rule and the per-token checks are vacuous for n == 0, so a proposal with empty arrays and a non-zero dataHash publishes and activates. Once the active basket is empty, the next proposal is treated like the first basket and may introduce five new members at once, so the documented hard rule (\"at most MaxAdditionsPerEpoch (2) new members per epoch\", listed in SECURITY.md as the limit on a malicious quorum) binds only while signers choose to keep a non-empty basket. Side effect: with an empty active basket every held position has target 0, so the keeper may liquidate everything through the exit path, without window or drift threshold. Fix: require n != 0 in _checkBasket, and/or count additions against the last non-empty basket.","line":332,"path":"src/EpochManager.sol","reproduction":"State: active epoch 1 with T0..T4, MaxAdditionsPerEpoch 2, nine approved tokens, quorum 2. (1) After 7 days publish epoch 2 with tokens=[], weightsBps=[], market arrays=[], dataHash != 0, fresh snapshot, quorum signatures -> accepted; activate after the delay -> activeBasket().tokens.length == 0. (2) After 7 more days publish epoch 3 with five tokens none of which were in epoch 1 -> expected TooManyAdditions, actual accepted and activated with 5 members. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_N -vv.","severity":"low","snippet":"        bool hadBasket = _active.tokens.length != 0;","title":"MaxAdditionsPerEpoch is bypassed by an empty basket: a zero-token proposal passes every rule and resets the turnover counter"},{"citation":"resolved","description":"Merged from audit_economics and audit_flow. README (\"2 days from a new epoch or once per interval\") and docs/METHODOLOGY.md (\"RebalanceWindow ... that opens with a new epoch\") anchor the window to activation. `_requireWindow` instead anchors it to the first non-exit trade after the epoch changes: while windowEpoch != epochs.epoch() the check always passes and sets windowStart = block.timestamp, and the \"once per RebalanceInterval\" re-opening is measured from that keeper-chosen moment. A keeper can leave a basket untraded for weeks and open a fresh two-day window whenever it suits it or its counterparty (still bounded by the oracle floor and delta rules, hence low). The suite's test_tradesOutsideTheWindowWaitForTheNextInterval measures the window from the first trade, so it does not catch this. Fix: expose activatedAt through IEpochManager and set windowStart = activatedAt when the epoch changes, re-opening windows on the interval schedule derived from it.","line":271,"path":"src/RebalanceExecutor.sol","reproduction":"State: epoch 1 activated at time A with five members and material deficits; no keeper trade. At A + 20 days the keeper calls executeTrade(USDC, member, deficit, 0, router, swapData). Expected (documented schedule [A, A+2d], [A+7d, A+9d], [A+14d, A+16d], [A+21d, A+23d]): revert WindowClosed. Actual: the trade succeeds and executor.windowStart() == A + 20 days. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_M -vv.","severity":"low","snippet":"        if (current != windowEpoch) {\n            windowEpoch = current;\n            windowStart = uint64(block.timestamp);","title":"The rebalance window opens at the keeper's first trade, not at epoch activation, so the keeper chooses when the two-day window runs"},{"citation":"resolved","description":"From audit_permissions. executeTrade increments failureCount[token] on every caught failure and quarantines once the count reaches FailureThreshold; the counter is cleared only by a successful trade of that token (line 119). AssetRegistry.releaseQuarantine (timelock, after minDelay) clears the quarantine flag but the executor's counter stays at the threshold, so the very next failure (including a mis-routed or sandwiched keeper call) trips `failures >= threshold && !isQuarantined` immediately. The documented three-consecutive-failures rule degrades to one after any release and the timelock has no way to reset the counter. Fix: reset the counter when the stored count already exceeds the threshold while the token is not quarantined (i.e. a release happened), or expose a timelock-only reset to be executed alongside releaseQuarantine.","line":124,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket bought; T1's feed quadruples so T1 is over target. Keeper submits three executeTrade(T1, USDC, 1e18, 0, router, router.fail()) -> each (false,0); third auto-quarantines T1; failureCount(T1) == 3. Timelock executes registry.releaseQuarantine(T1): isQuarantined(T1) == false, failureCount(T1) still 3. One more failing executeTrade(T1, ...) -> isQuarantined(T1) == true again. Expected: three new consecutive failures required. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_D.","severity":"low","snippet":"            uint256 failures = ++failureCount[token];\n            if (failures >= registry.param(Param.FailureThreshold) && !registry.isQuarantined(token)) {","title":"failureCount is not reset by releaseQuarantine, so one failed trade after a release re-quarantines the token although FailureThreshold is 3"},{"citation":"resolved","description":"From audit_economics. `_authorize` refuses any sale whose fair output rounds to zero. An exit that leaves even 1 wei of an 18-decimal token behind (a venue that fills the exact amount minus dust, a partial fill whose unsold remainder is returned, or a keeper selling balance - 1) leaves a position the keeper can never clear: every later sale reverts NothingToTrade and endTrade drops a token only when its balance is exactly zero. The slot stays occupied in _held (MAX_HELD = 10), the token stays in every NAV, redemption and holdings loop, and its feed must stay fresh or deposits close. Five such leftovers block the purchase of any new member (HeldListFull). Only the timelock can clean up via quarantine plus disposeQuarantined with minOut = 1 through a venue willing to pay one unit for dust. Fix: when exit is true and the whole balance is being sold, allow fair == 0 with oracleMinOut = 0 (there is no value to protect), or let endTrade drop a held token whose registry.convert(balance) is zero.","line":264,"path":"src/RebalanceExecutor.sol","reproduction":"State: basket bought; guardian quarantines T0 (vault holds 98e18 T0). Keeper executeTrade(T0, USDC, balance - 1, 0, router, fair fill) -> (true, ...). tokens[0].balanceOf(vault) == 1; vault.isHeld(T0) == true. Then executeTrade(T0, USDC, 1, 0, router, router.swap(T0, USDC, 1, 1)) -> reverts NothingToTrade. Expected: an exited position is fully clearable. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_K.","severity":"low","snippet":"        if (fair == 0) revert NothingToTrade();","title":"A position worth less than one reserve unit can never be sold through the keeper path and keeps its held slot"},{"citation":"resolved","description":"From audit_permissions. `_valueOf` uses IERC20(token).balanceOf(vault) whether or not the vault tracks the token, while IndexVault._nav, redeem, previewRedeem and the executor's own _nav iterate only _held, which grows solely through endTrade. A member never bought (or dropped after being sold to zero) is priced into `current` by the executor but into nothing by the vault. Anyone can transfer such a token to the vault: the buy branch then reverts NothingToTrade (current >= target) and the sell branch reverts NotHeld, so the keeper cannot bring the member into the basket, NAV ignores the balance and redeemers receive none of it. Only the timelock's rescue (token not held) can move it after minDelay. The griefer forfeits the donated tokens, which bounds the severity. Fix: have _valueOf consult vault.isHeld and treat untracked balances as zero (consistent with _nav), or let the vault adopt an untracked member balance into _held.","line":299,"path":"src/RebalanceExecutor.sol","reproduction":"State: active basket {T0 20%, T1 20%}, vault holds 1,000,000 USDC and nothing else; executor.position(T0) gives current 0, target 196,000e6. Mallory mints/transfers ~99e18 T0 ($2000 each) straight to the vault. vault.nav() still 1,000,000e6; isHeld(T0) false; position(T0).current >= target. Keeper executeTrade(USDC, T0, 1000e6, ...) -> reverts NothingToTrade(); executeTrade(T0, USDC, 1e18, ...) -> reverts NotHeld(T0). Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_C.","severity":"low","snippet":"        uint256 balance = IERC20(token).balanceOf(address(vault));","title":"Executor values a member by the vault's raw balance while the vault's NAV and redemptions count only the held list, so tokens sent directly to the vault block buying that member"},{"citation":"resolved","description":"From audit_economics. pushBasketReserve(minShares) is permissionless and deposits the whole accrued basket bucket at the vault's feed NAV for shares owned by the timelock, with the caller choosing the moment and the floor (minShares may be 0). Whenever feeds are stale-high (market down by less than the deviation threshold, or a feed about to be lowered), a share holder calls pushBasketReserve(0) and the treasury overpays; the difference accrues to the existing holders, including the caller. Bounded by the deviation threshold and the bucket size (a 10,000 USDC push on a fully invested vault with feeds 1.9% above market loses the treasury about 186 USDC per push, repeatable on every accumulation), hence low. Fix: restrict pushBasketReserve to a role (keeper or timelock) or compute minShares on-chain from previewDeposit with a timelock-set tolerance so a caller cannot choose a zero floor.","line":129,"path":"src/FeeWaterfall.sol","reproduction":"State: fully invested 1,000,000 USDC vault; 10,000 USDC minted to the waterfall (basket share 5,333 after the 40/75 split). Any address (BOB) calls waterfall.pushBasketReserve(0): succeeds, shares minted to the timelock at the current feed NAV with no floor. Valuing the treasury's previewRedeem at 98.1% of the feed price gives 5,234.55 USDC for the 5,333.33 deposited. Verified mechanics: forge test --match-path test/scratch/Leads.t.sol --match-test test_L -vv.","severity":"low","snippet":"        shares = vault.deposit(amount, address(admin), minShares);","title":"Anyone chooses when the treasury buys vault shares with the basket reserve, with minShares 0"},{"citation":"resolved","description":"From audit_math. EpochManager._checkTiming requires p.expiry > readyAt (= now + ProposalDelay) and p.expiry <= now + MAX_VALIDITY (7 days). With ProposalDelay == 7 days both cannot hold, and paramBounds admits exactly that value, so one timelocked change puts the system into a state where no basket can be published until another timelocked change reverts it. Fix: bound ProposalDelay strictly below MAX_VALIDITY (e.g. hi = 7 days - 1 hours) or make the ExpiryTooFar test relative to readyAt.","line":221,"path":"src/AssetRegistry.sol","reproduction":"Fixture; registry.setParam(Param.ProposalDelay, 7 days) through the timelock succeeds. A valid quorum-signed top-five proposal with expiry = now + 7 days -> reverts ExpiryTooSoon; expiry = now + 7 days + 1 -> ExpiryTooFar; expiry = now + 1 day -> ExpiryTooSoon. No expiry value is accepted. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_O.","severity":"low","snippet":"        if (key == Param.ProposalDelay) return (1 hours, 7 days);","title":"ProposalDelay upper bound equals MAX_VALIDITY, so the allowed value 7 days makes every proposal unpublishable"},{"citation":"resolved","description":"From audit_math. The reserve asset's decimals are supplied twice in the launch manifest, to AssetRegistry and to IndexVault. README says the vault refuses its first deposit if reserveDecimals does not match the token, but that check (IndexVault.sol:120) validates only the vault's copy. The registry's copy feeds _decimalsOf() and therefore every convert() result: NAV, targets, the ExceedsDelta bound and the oracle floor. Nothing compares it to the token or the vault. The committed launch.json is consistent (WETH, 18 and 18), so this is a robustness defect for the configuration path rather than a live bug. Fix: have IndexVault's first-deposit check also require registry.reserveDecimals() == decimals(), or have AssetRegistry read IERC20Metadata(reserveAsset).decimals() lazily on first use.","line":84,"path":"src/AssetRegistry.sol","reproduction":"Deploy AssetRegistry(admin, USDC, 18) beside the correct AssetRegistry(admin, USDC, 6); configure the same reserve feed and approve T0 ($2000, 18 dec) in both. wrong.convert(USDC, 196_000e6, T0) returns 98,000,000 wei; the correct registry returns 98e18. A keeper trade of 196,000 USDC for T0 would pass the wrong registry's floor with 1e12 times too little T0. Verified: forge test --match-path test/scratch/Leads.t.sol --match-test test_P -vv.","severity":"low","snippet":"        reserveDecimals = reserveDecimals_;","title":"AssetRegistry's reserveDecimals is never verified against the token or the vault; a mismatch mis-scales every price conversion and the keeper's oracle floor by 10^k"}],"hash":"be24de4edcb027ea9b4b15b1cf69cfb3175e63c79c8476eee01f93b9a6d07acb","nodeId":"c4c8b65f-1a08-4b70-9b30-8abf9ae97ad4","outcome":"completed","summary":"Review complete. The findings file is written and validated, and the working tree holds only the required output and the allowed `test/scratch/` reproductions.\n\n**Outcome.** I reproduced every specialist claim against the code and kept 17 findings after merging duplicates: 1 high, 7 medium, 9 low. Nothing was dropped as irreproducible. All 57 verifier entry points have a coverage row, with 47 holding and 12 carrying a finding, plus two invariant rows. The project's own suite passes as a baseline (246 tests).\n\n**Merges made.**\n- Guardian veto (permissions high + flow medium) became one high. Both proofs fail with `NotScheduled()`. I rated it high because the guardian can freeze everything except in-kind redemption with no recovery path, and basket fees then strand in the waterfall forever.\n- Hook partial-fill fee (economics medium + math low) became one medium, with my own self-contained proof against the real PoolManager. A seller of IMDEX with a tight price limit ends up paying 6.5 quote plus IMDEX, and an exact-input buy pays a 7,500 fee on about 350,715 filled.\n- Strict-redeem forfeit (tests medium + math low + economics low) became one medium, carrying the tests specialist's failing proof.\n- Executor uncapped `balanceOf` (permissions medium + economics low) became one medium. My reproduction shows keeper trades revert at both 30M and 10M gas while the vault's capped redeem survives, which matches the permissions specialist rather than the economics one.\n- Window-opens-on-first-trade (economics + flow) merged to one low. Keeper auto-quarantine liquidation absorbed the flow \"info\" item about third-party-caused failures.\n\n**Proofs attached** to the four most severe: guardian veto, hook partial fill, strict redemption, and stale snapshot at activation. Each was run from `test/scratch/` and fails on the current code for the stated reason.\n\n**Severity judgments worth noting.** I kept the same-block oracle-lag arbitrage at medium even though the README names feed latency as a limitation, because the round trip is atomic and risk-free and the default deposit fee is zero. I kept the keeper liquidation at medium because the keeper enlarges its documented 1% per-trade exposure to the whole basket and can also veto epoch activation by quarantining a pending member. The registry decimals finding is low and not live: the committed manifest is consistent.\n\n**Own pass.** Reading the code again with the supplied checklists did not surface a defect the four specialists missed. The nearest leads (permissionless `quarantineIfStale` opening the exit path on a transiently late feed, and keeper-driven activation vetoes) share a root cause with the keeper finding and are folded into it.","treeHash":null,"usage":{"cachedInputTokens":3168991,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":95891,"runtime":"claude","turns":57,"wallClockMs":1440205}},{"artifacts":[],"attempt":1,"bundleHash":"d5b6421e1c7544a5f83d1370f11d617bf3ac87d8e87e7eb893ad02ea1ff759f1","device":"ca720d6a84c31fdd","findings":[],"hash":"ee4bd8bbc805b0fd10bd620c15f7a690a35452944ef02082021a66ccf9fddfd5","nodeId":"9365e8f1-318d-4720-be78-e410ebc31c87","outcome":"completed","summary":"Created [launch.json](/home/imd-worker/.identitymd/work/6321056b-a125-48f2-9c7a-81cc8695a1f2/9365e8f1-318d-4720-be78-e410ebc31c87/launch.json) with seven contracts in dependency order and ETH/WETH configuration.\n\nSchema and constructor checks passed. `forge build` succeeded; `forge test` passed all 224 tests, including protected checks.\n\nOnly `launch.json` changed outside permitted scratch scaffolding.","treeHash":"4c830226d1c1bc3e163f216f01ea5106446e3a86","usage":{"cachedInputTokens":476032,"inputTokens":60135,"model":"gpt-6-astra","outputTokens":6862,"runtime":"codex","turns":4,"wallClockMs":250619}},{"artifacts":[],"attempt":2,"bundleHash":"515f8b3d180e0150360f58a1b55f005a5156709491e7f53ac020f78fef01290f","device":"5d0460745287d96a","findings":[{"description":"The buy branch checks membership, approval and quarantine, but never registry.isEligible or the current maxWeightBps. _target uses the active proposal weight without applying the current cap. After a legitimate timelocked tightening, a keeper can continue buying a now-ineligible token or build exposure above its new cap using an older active basket. This is not an unauthorized configuration change: the proof uses the real admin delay to tighten protections, then an ordinary keeper bypasses those protections. Enforce current eligibility on new purchases and apply current weight caps (or invalidate execution of baskets signed under superseded rules).","line":233,"path":"src/RebalanceExecutor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {EpochManager} from \"src/EpochManager.sol\";\nimport {IndexVault} from \"src/IndexVault.sol\";\nimport {RebalanceExecutor} from \"src/RebalanceExecutor.sol\";\nimport {Param} from \"src/interfaces/IIndex.sol\";\n\ncontract RiskProofToken {\n    uint8 public immutable decimals;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    constructor(uint8 d) { decimals = d; }\n    function mint(address to, uint256 n) external { balanceOf[to] += n; }\n    function approve(address to, uint256 n) external returns (bool) { allowance[msg.sender][to] = n; return true; }\n    function transfer(address to, uint256 n) external returns (bool) {\n        balanceOf[msg.sender] -= n; balanceOf[to] += n; return true;\n    }\n    function transferFrom(address from, address to, uint256 n) external returns (bool) {\n        allowance[from][msg.sender] -= n; balanceOf[from] -= n; balanceOf[to] += n; return true;\n    }\n}\n\ncontract RiskProofFeed {\n    function decimals() external pure returns (uint8) { return 8; }\n    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (1, 1e8, block.timestamp, block.timestamp, 1);\n    }\n}\n\ncontract RiskProofRouter {\n    function swap(RiskProofToken reserve, RiskProofToken asset, uint256 amount) external {\n        reserve.transferFrom(msg.sender, address(this), amount);\n        asset.transfer(msg.sender, amount * 1e12);\n    }\n}\n\nabstract contract LiveRiskProofBase is Test {\n    address constant OWNER = address(0x1001);\n    address constant GUARDIAN = address(0x1002);\n    address constant KEEPER = address(0x1003);\n    uint256 constant SIGNER_KEY = 0xCAFE;\n    TimelockedAdmin admin;\n    AssetRegistry registry;\n    EpochManager epochs;\n    IndexVault vault;\n    RebalanceExecutor executor;\n    RiskProofToken reserve;\n    RiskProofToken token;\n    RiskProofFeed feed;\n    RiskProofRouter router;\n    uint256 nonce;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(OWNER, 1 days);\n        reserve = new RiskProofToken(6);\n        token = new RiskProofToken(18);\n        feed = new RiskProofFeed();\n        registry = new AssetRegistry(address(admin), address(reserve), 6);\n        epochs = new EpochManager(address(admin), address(registry));\n        vault = new IndexVault(address(admin), address(registry), address(reserve), 6, 1_000_000e6);\n        executor = new RebalanceExecutor(address(admin), address(registry), address(epochs), address(vault));\n        router = new RiskProofRouter();\n        address[] memory targets = new address[](8);\n        bytes[] memory calls = new bytes[](8);\n        for (uint256 i; i < 5; ++i) targets[i] = address(admin);\n        for (uint256 i = 5; i < 8; ++i) targets[i] = address(registry);\n        calls[0] = abi.encodeCall(admin.setGuardian, (GUARDIAN));\n        calls[1] = abi.encodeCall(admin.setExecutor, (address(executor)));\n        calls[2] = abi.encodeCall(admin.setKeeper, (KEEPER, true));\n        calls[3] = abi.encodeCall(admin.setSigner, (vm.addr(SIGNER_KEY), true));\n        calls[4] = abi.encodeCall(admin.setQuorum, (1));\n        calls[5] = abi.encodeCall(registry.setReserveFeed, (address(feed), 1 days));\n        calls[6] = abi.encodeCall(registry.setRouter, (address(router), true));\n        calls[7] = abi.encodeCall(registry.approveToken,\n            (address(token), address(feed), 1 days, 2000, uint40(block.timestamp - 31 days), keccak256(\"review\")));\n        for (uint256 i; i < 8; ++i) {\n            vm.prank(OWNER);\n            admin.schedule(targets[i], 0, calls[i], bytes32(i), 1 days);\n        }\n        vm.warp(block.timestamp + 1 days);\n        for (uint256 i; i < 8; ++i) {\n            vm.prank(OWNER);\n            admin.execute(targets[i], 0, calls[i], bytes32(i));\n        }\n        reserve.mint(address(this), 100_000e6);\n        reserve.approve(address(vault), 100_000e6);\n        vault.deposit(100_000e6, address(this), 0);\n        vm.roll(block.number + 1);\n        token.mint(address(router), 100_000e18);\n    }\n\n    function _proposal() internal view returns (EpochManager.Proposal memory p) {\n        p.epoch = 1;\n        p.snapshotTime = uint64(block.timestamp);\n        p.expiry = uint64(block.timestamp + 23 hours);\n        p.methodologyVersion = registry.methodologyVersion();\n        p.signerSetVersion = admin.signerSetVersion();\n        p.dataHash = keccak256(\"daily report\");\n        p.tokens = new address[](1); p.tokens[0] = address(token);\n        p.weightsBps = new uint16[](1); p.weightsBps[0] = 2000;\n        p.marketCapsUsd = new uint256[](1); p.marketCapsUsd[0] = 1_000_000_000;\n        p.liquidityUsd = new uint256[](1); p.liquidityUsd[0] = 50_000_000;\n        p.volumesUsd = new uint256[](1); p.volumesUsd[0] = 50_000_000;\n    }\n\n    function _publish() internal {\n        EpochManager.Proposal memory p = _proposal();\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, epochs.hashProposal(p));\n        bytes[] memory sigs = new bytes[](1); sigs[0] = abi.encodePacked(r, s, v);\n        epochs.publish(p, sigs);\n    }\n\n    function _activate() internal {\n        _publish();\n        vm.warp(block.timestamp + 6 hours);\n        epochs.activate();\n    }\n\n    function _govern(bytes memory data) internal {\n        bytes32 salt = bytes32(++nonce + 100);\n        vm.prank(OWNER);\n        admin.schedule(address(registry), 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(OWNER);\n        admin.execute(address(registry), 0, data, salt);\n    }\n\n    function _assertBuyRejected() internal {\n        vm.prank(KEEPER);\n        (bool callOk, bytes memory ret) = address(executor).call(abi.encodeCall(executor.executeTrade,\n            (address(reserve), address(token), 10_000e6, 0, address(router),\n             abi.encodeCall(router.swap, (reserve, token, 10_000e6)))));\n        bool traded;\n        if (callOk) (traded,) = abi.decode(ret, (bool, uint256));\n        assertFalse(traded, \"purchase bypassed the live risk rule\");\n        assertEq(reserve.balanceOf(address(vault)), 100_000e6);\n        assertEq(token.balanceOf(address(vault)), 0);\n    }\n\n}\n\ncontract ExecutorRulesProof is LiveRiskProofBase {\n    function test_liveAgeRuleMustStopAnExistingBasketPurchase() public {\n        _activate();\n        _govern(abi.encodeCall(registry.setParam, (Param.MinTokenAge, 90 days)));\n        assertFalse(registry.isEligible(address(token)), \"precondition: age rule must actually exclude token\");\n        _assertBuyRejected();\n    }\n\n    function test_liveWeightCapMustStopAnExistingBasketPurchase() public {\n        _activate();\n        AssetRegistry.Asset memory a = registry.asset(address(token));\n        _govern(abi.encodeCall(registry.approveToken,\n            (address(token), a.feed, a.heartbeat, 100, a.listedAt, a.reviewHash)));\n        assertEq(registry.maxWeightBps(address(token)), 100);\n        // 10,000 USDC is 10% of NAV and exceeds the new 1% cap.\n        _assertBuyRejected();\n    }\n\n}","reproduction":"Run the supplied proof as test/ExecutorRulesProof.t.sol with forge test --match-path test/ExecutorRulesProof.t.sol. Deposit 100,000 USDC and activate a one-token basket at 2,000 bps, with a fresh $1 oracle and a token initially over 30 days old. Case 1: schedule and execute MinTokenAge=90 days through the real one-day timelock; registry.isEligible(token) is now false. Case 2 (fresh setup): reduce maxWeightBps from 2,000 to 100 through the same timelock. In either case, within the open two-day rebalance window, the keeper sells 10,000 USDC for 10,000 tokens at the oracle price. Expected: the purchase is rejected and reserve remains 100,000 USDC. Actual: executeTrade returns (true, 10,000e18), reserve falls to 90,000 USDC and the vault holds 10,000 tokens. In case 2 that is 10% of NAV, above both the 1% gross-NAV cap and the 980-USDC buffered target. Both tests fail with purchase bypassed the live risk rule.","severity":"medium","title":"Active-basket purchases bypass subsequently tightened age and weight limits"},{"description":"publish checks signed market cap, liquidity and volume against current minimums, but Pending does not retain these values or a risk-configuration version. activate rechecks token eligibility and weight caps only. A proposal published while a timelocked increase is pending can therefore become the active basket after that increase executes even though its signed data violate the current hard rules. Changing numeric minimums does not change methodologyVersion. Persist and revalidate the attested data, or invalidate pending proposals when relevant risk rules change.","line":194,"path":"src/EpochManager.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TimelockedAdmin} from \"src/TimelockedAdmin.sol\";\nimport {AssetRegistry} from \"src/AssetRegistry.sol\";\nimport {EpochManager} from \"src/EpochManager.sol\";\nimport {IndexVault} from \"src/IndexVault.sol\";\nimport {RebalanceExecutor} from \"src/RebalanceExecutor.sol\";\nimport {Param} from \"src/interfaces/IIndex.sol\";\n\ncontract RiskProofToken {\n    uint8 public immutable decimals;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    constructor(uint8 d) { decimals = d; }\n    function mint(address to, uint256 n) external { balanceOf[to] += n; }\n    function approve(address to, uint256 n) external returns (bool) { allowance[msg.sender][to] = n; return true; }\n    function transfer(address to, uint256 n) external returns (bool) {\n        balanceOf[msg.sender] -= n; balanceOf[to] += n; return true;\n    }\n    function transferFrom(address from, address to, uint256 n) external returns (bool) {\n        allowance[from][msg.sender] -= n; balanceOf[from] -= n; balanceOf[to] += n; return true;\n    }\n}\n\ncontract RiskProofFeed {\n    function decimals() external pure returns (uint8) { return 8; }\n    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (1, 1e8, block.timestamp, block.timestamp, 1);\n    }\n}\n\ncontract RiskProofRouter {\n    function swap(RiskProofToken reserve, RiskProofToken asset, uint256 amount) external {\n        reserve.transferFrom(msg.sender, address(this), amount);\n        asset.transfer(msg.sender, amount * 1e12);\n    }\n}\n\nabstract contract LiveRiskProofBase is Test {\n    address constant OWNER = address(0x1001);\n    address constant GUARDIAN = address(0x1002);\n    address constant KEEPER = address(0x1003);\n    uint256 constant SIGNER_KEY = 0xCAFE;\n    TimelockedAdmin admin;\n    AssetRegistry registry;\n    EpochManager epochs;\n    IndexVault vault;\n    RebalanceExecutor executor;\n    RiskProofToken reserve;\n    RiskProofToken token;\n    RiskProofFeed feed;\n    RiskProofRouter router;\n    uint256 nonce;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        admin = new TimelockedAdmin(OWNER, 1 days);\n        reserve = new RiskProofToken(6);\n        token = new RiskProofToken(18);\n        feed = new RiskProofFeed();\n        registry = new AssetRegistry(address(admin), address(reserve), 6);\n        epochs = new EpochManager(address(admin), address(registry));\n        vault = new IndexVault(address(admin), address(registry), address(reserve), 6, 1_000_000e6);\n        executor = new RebalanceExecutor(address(admin), address(registry), address(epochs), address(vault));\n        router = new RiskProofRouter();\n        address[] memory targets = new address[](8);\n        bytes[] memory calls = new bytes[](8);\n        for (uint256 i; i < 5; ++i) targets[i] = address(admin);\n        for (uint256 i = 5; i < 8; ++i) targets[i] = address(registry);\n        calls[0] = abi.encodeCall(admin.setGuardian, (GUARDIAN));\n        calls[1] = abi.encodeCall(admin.setExecutor, (address(executor)));\n        calls[2] = abi.encodeCall(admin.setKeeper, (KEEPER, true));\n        calls[3] = abi.encodeCall(admin.setSigner, (vm.addr(SIGNER_KEY), true));\n        calls[4] = abi.encodeCall(admin.setQuorum, (1));\n        calls[5] = abi.encodeCall(registry.setReserveFeed, (address(feed), 1 days));\n        calls[6] = abi.encodeCall(registry.setRouter, (address(router), true));\n        calls[7] = abi.encodeCall(registry.approveToken,\n            (address(token), address(feed), 1 days, 2000, uint40(block.timestamp - 31 days), keccak256(\"review\")));\n        for (uint256 i; i < 8; ++i) {\n            vm.prank(OWNER);\n            admin.schedule(targets[i], 0, calls[i], bytes32(i), 1 days);\n        }\n        vm.warp(block.timestamp + 1 days);\n        for (uint256 i; i < 8; ++i) {\n            vm.prank(OWNER);\n            admin.execute(targets[i], 0, calls[i], bytes32(i));\n        }\n        reserve.mint(address(this), 100_000e6);\n        reserve.approve(address(vault), 100_000e6);\n        vault.deposit(100_000e6, address(this), 0);\n        vm.roll(block.number + 1);\n        token.mint(address(router), 100_000e18);\n    }\n\n    function _proposal() internal view returns (EpochManager.Proposal memory p) {\n        p.epoch = 1;\n        p.snapshotTime = uint64(block.timestamp);\n        p.expiry = uint64(block.timestamp + 23 hours);\n        p.methodologyVersion = registry.methodologyVersion();\n        p.signerSetVersion = admin.signerSetVersion();\n        p.dataHash = keccak256(\"daily report\");\n        p.tokens = new address[](1); p.tokens[0] = address(token);\n        p.weightsBps = new uint16[](1); p.weightsBps[0] = 2000;\n        p.marketCapsUsd = new uint256[](1); p.marketCapsUsd[0] = 1_000_000_000;\n        p.liquidityUsd = new uint256[](1); p.liquidityUsd[0] = 50_000_000;\n        p.volumesUsd = new uint256[](1); p.volumesUsd[0] = 50_000_000;\n    }\n\n    function _publish() internal {\n        EpochManager.Proposal memory p = _proposal();\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, epochs.hashProposal(p));\n        bytes[] memory sigs = new bytes[](1); sigs[0] = abi.encodePacked(r, s, v);\n        epochs.publish(p, sigs);\n    }\n\n    function _activate() internal {\n        _publish();\n        vm.warp(block.timestamp + 6 hours);\n        epochs.activate();\n    }\n\n    function _govern(bytes memory data) internal {\n        bytes32 salt = bytes32(++nonce + 100);\n        vm.prank(OWNER);\n        admin.schedule(address(registry), 0, data, salt, 1 days);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(OWNER);\n        admin.execute(address(registry), 0, data, salt);\n    }\n\n    function _assertBuyRejected() internal {\n        vm.prank(KEEPER);\n        (bool callOk, bytes memory ret) = address(executor).call(abi.encodeCall(executor.executeTrade,\n            (address(reserve), address(token), 10_000e6, 0, address(router),\n             abi.encodeCall(router.swap, (reserve, token, 10_000e6)))));\n        bool traded;\n        if (callOk) (traded,) = abi.decode(ret, (bool, uint256));\n        assertFalse(traded, \"purchase bypassed the live risk rule\");\n        assertEq(reserve.balanceOf(address(vault)), 100_000e6);\n        assertEq(token.balanceOf(address(vault)), 0);\n    }\n\n}\n\ncontract EpochRulesProof is LiveRiskProofBase {\n    function test_activationMustRecheckATightenedMarketCapFloor() public {\n        bytes memory data = abi.encodeCall(registry.setParam, (Param.MinMarketCapUsd, 2_000_000_000));\n        vm.prank(OWNER);\n        admin.schedule(address(registry), 0, data, bytes32(\"tighten\"), 1 days);\n        vm.warp(block.timestamp + 20 hours);\n        _publish(); // Signed market cap = $1bn, valid under the original $250m minimum.\n        vm.warp(block.timestamp + 6 hours);\n        vm.prank(OWNER);\n        admin.execute(address(registry), 0, data, bytes32(\"tighten\"));\n        assertEq(registry.param(Param.MinMarketCapUsd), 2_000_000_000);\n        (bool activated,) = address(epochs).call(abi.encodeCall(epochs.activate, ()));\n        assertFalse(activated, \"activated $1bn asset under the current $2bn minimum\");\n        assertEq(epochs.epoch(), 0);\n    }\n}","reproduction":"Run the supplied proof as test/EpochRulesProof.t.sol with forge test --match-path test/EpochRulesProof.t.sol. Schedule MinMarketCapUsd=2,000,000,000 with a one-day timelock. After 20 hours, publish a valid quorum-signed proposal attesting a $1,000,000,000 market cap, above the original $250,000,000 minimum. After another six hours, execute the matured configuration change, then call activate. Prices are fresh, the snapshot is six hours old, and expiry is still 17 hours away. Expected: activation fails because the signed $1bn market cap is below the now-effective $2bn minimum and epoch stays 0. Actual: activation succeeds and epoch becomes 1. The proof fails with activated $1bn asset under the current $2bn minimum.","severity":"medium","title":"Activation ignores market-data minimums raised during the proposal delay"}],"hash":"f21a5ac7d491dd15bf0ffe4b7f7b5135ba2b22429013618f3f301b8302d3195b","nodeId":"e99918fb-1eef-464c-a358-fddddeb2126a","outcome":"completed","summary":"Added adversarial tests and two stateful invariant campaigns.\n\n- `forge build` passed.\n- `forge test`: **267 passed, 0 failed**.\n- Reported two medium-severity defects with failing proofs in `.imd-findings.json`: execution bypasses tightened age/weight limits, and activation ignores raised market-data minimums.\n\nMainnet-fork validation remains outstanding.","treeHash":"22ba9f64090f691bd9cb29bdf30778f259293ced","usage":{"cachedInputTokens":2477312,"inputTokens":136916,"model":"gpt-6-astra","outputTokens":25135,"runtime":"codex","turns":8,"wallClockMs":751799}}],"verification":[{"checks":[{"durationMs":22020,"exitCode":0,"name":"build","output":"Compiling 114 files with Solc 0.8.26\nSolc 0.8.26 finished in 21.71s\nCompiler run successful!\n","passed":true},{"durationMs":4009,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85411, ~: 85564)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 4.26ms (5.19ms CPU time)\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 106978)\n[PASS] test_adminHandoverIsTwoStep() (gas: 483912)\n[PASS] test_adminWalletCannotHoldARole() (gas: 105652)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168689)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 140963)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 107461)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 98982)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 291612)\n[PASS] test_executorMustBeAContract() (gas: 116653)\n[PASS] test_guardianCanOnlyTighten() (gas: 256933)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 157844)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 398614)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 110044)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 124107)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 472150)\n[PASS] test_quorumBounds() (gas: 104597)\n[PASS] test_quorumCannotExceedSigners() (gas: 106729)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68190)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 159131)\n[PASS] test_signerCannotBecomeGuardian() (gas: 144280)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 487684)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 129127)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 21.29ms (4.01ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 386123, ~: 387872)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 374122)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352194)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114689)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25638654)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 240738)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 698941)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 365253)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162729)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1186286)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810786)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 746181)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 299243)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 356623)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 431410)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 90.79ms (114.47ms CPU time)\n\nRan 26 tests for test/Revision.t.sol:RevisionTest\n[PASS] test_activationAtSnapshotAgeBoundarySucceeds() (gas: 1196785)\n[PASS] test_activationRechecksSnapshotAgeWithFreshPrices() (gas: 865609)\n[PASS] test_adminCanCancelRecoveryAndGuardianStillVetoesOtherCalls() (gas: 329121)\n[PASS] test_autoQuarantineDoesNotVetoProposalOrCloseFairDeposits() (gas: 3891522)\n[PASS] test_autoQuarantineRetainsDeltaAndWindowUntilGuardianConfirms() (gas: 6751411)\n[PASS] test_deadUntransferablePositionPreservesClaimsAndClosesDeposits() (gas: 5557737)\n[PASS] test_dustDepositCannotLockExistingShares() (gas: 5352326)\n[PASS] test_emptyBasketCannotResetTurnoverHistory() (gas: 1836695)\n[PASS] test_firstDepositChecksRegistryReserveScale() (gas: 157196)\n[PASS] test_firstTradeCannotChooseWindowStart() (gas: 1827439)\n[PASS] test_fullDustExitCanFreeHeldSlot() (gas: 5073260)\n[PASS] test_gasBurningUnquarantinedBalanceFailsWithBoundedError() (gas: 5596809)\n[PASS] test_guardianCannotVetoReplacementOrUnpause() (gas: 881301)\n[PASS] test_heldQuarantineClosesDepositsEvenWithFreshFeed() (gas: 4626889)\n[PASS] test_keeperFailuresCannotCreateExitRightsForAnyMember() (gas: 13567352)\n[PASS] test_lateFirstTradeUsesTheActivationSchedule() (gas: 2526980)\n[PASS] test_newSharesCannotRedeemInTheirDepositBlock() (gas: 4967089)\n[PASS] test_newSharesCannotTransferOrTransferFromToBypassLock() (gas: 4757506)\n[PASS] test_onlyKeeperOrTimelockChoosesTreasuryDepositTime() (gas: 504557)\n[PASS] test_proposalDelayAndSnapshotAgeMustLeaveExecutionTime() (gas: 1322352)\n[PASS] test_proposalDelayCannotConsumeEntireValidityPeriod() (gas: 33021)\n[PASS] test_quarantinedGasBurnerCannotStopUnrelatedTrades() (gas: 5283723)\n[PASS] test_releaseStartsANewFailureStreak() (gas: 6550330)\n[PASS] test_strictUnreadableBalancePreservesSharesAfterHoldingPeriod() (gas: 4506318)\n[PASS] test_unreadableNonStrictRedemptionEmitsUnknownSkippedSlice() (gas: 4549019)\n[PASS] test_untrackedDonationDoesNotBlockMemberPurchase() (gas: 1968485)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 90.74ms (148.38ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 32922360)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 32968008)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 33039419)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 62564150)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 164.89ms (79.86ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 385236, ~: 383191)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 380397)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357085)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114108)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25733539)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 247265)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 703892)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 371441)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162204)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1185543)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809916)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 752656)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 292962)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 351230)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 436368)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 164.89ms (102.69ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 319274, ~: 314123)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 325161)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301553)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114036)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 166127646)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 214260)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 657984)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 314151)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 973549)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1129678)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809832)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 330690)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 638324)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 242556)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 288953)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 406745)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 165.02ms (172.27ms CPU time)\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 421281, ~: 428193)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 832760)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 928119)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1329292)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 422507)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 789148)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1518932)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94422)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 164.94ms (164.16ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 70668, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150827)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 759986)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 117462)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 117348)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 866232)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 364271)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167510)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178546)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23738)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41428)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 514624)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 109669)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 583244)\n[PASS] test_paramsStayInsideHardBounds() (gas: 473110)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53355)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177818)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 402781)\n[PASS] test_routerAllowlist() (gas: 396934)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 109471)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1623567)\n[PASS] test_windowCannotExceedInterval() (gas: 112139)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 512.71ms (506.77ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5834133, ~: 5845322)\n[PASS] test_depositCapBoundsTheBeta() (gas: 1002963)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 938312)\n[PASS] test_depositHonoursMinShares() (gas: 428641)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4976759)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185649)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 33037112)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 749946)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 478022)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 350195)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4694172)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4870581)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 536811)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 553925)\n[PASS] test_redeemPaysProRataInKind() (gas: 4879947)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 401017)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4849001)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 5108067)\n[PASS] test_shareTokenMetadata() (gas: 36983)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 5047402)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4746454)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154791)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 529.32ms (588.54ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1242734)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1418116)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1843795)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 980485)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798269)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67665)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338670)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964025)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199917)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1257669)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289433)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 287974)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216384)\n[PASS] test_rejectsBelowQuorum() (gas: 275755)\n[PASS] test_rejectsExpiredProposal() (gas: 149875)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694378)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150491)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513185)\n[PASS] test_rejectsMismatchedArrays() (gas: 469498)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163409)\n[PASS] test_rejectsQuarantinedToken() (gas: 243222)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807292)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289450)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859202)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 286896)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1696120)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1146000)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192211)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368746)\n[PASS] test_rejectsWrongEpoch() (gas: 191234)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157190)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508033)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496202)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 879267)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1330561)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 289972)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 689091)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 983533)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2298890)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2285681)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 529.39ms (65.98ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 453014, ~: 452980)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 4023605)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134150)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3184386)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 3035744)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2996731)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 533159)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 738772)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 487730)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 401293)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85362)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2965570)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1152550)\n[PASS] test_onlyKeepersTrade() (gas: 285301)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3243973)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 344462)\n[PASS] test_pauseStopsTrading() (gas: 130761)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3704159)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3680033)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5470643)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3237194)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 1024845)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3768650)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 390207)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4268519)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4295590)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3905897)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4799416)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 529.37ms (182.26ms CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 32, calls: 2048, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 316   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 306   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 290   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 292   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 300   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 277   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 267   | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.66s (3.63s CPU time)\n\nRan 13 test suites in 3.66s (6.63s CPU time): 247 tests passed, 0 failed, 0 skipped (247 total tests)\n","passed":true},{"durationMs":340,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":287,\"docs/METHODOLOGY.md\":139,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":95,\"foundry.toml\":34,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":336,\"src/EpochManager.sol\":382,\"src/FeeHook.sol\":258,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":242,\"src/IndexVault.sol\":368,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":344,\"src/TimelockedAdmin.sol\":271,\"src/interfaces/IIndex.sol\":91,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":560,\"test/FeeHook.t.sol\":433,\"test/FeeWaterfall.t.sol\":239,\"test/IndexVault.t.sol\":366,\"test/LaunchToken.t.sol\":69,\"test/RebalanceExecutor.t.sol\":488,\"test/Revision.t.sol\":426,\"test/TimelockedAdmin.t.sol\":272,\"test/invariant/Handler.sol\":165,\"test/invariant/Solvency.invariant.t.sol\":95,\"test/utils/Fixture.sol\":282,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":10612,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/RebalanceExecutor.sol:177: Reentrancy in RebalanceExecutor._swap(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#177-204):\n[high/medium] reentrancy-balance at src/RebalanceExecutor.sol:177: Reentrancy in RebalanceExecutor._swap(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#177-204):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:108: Reentrancy in FeeWaterfall.claim(FeeWaterfall.Bucket) (src/FeeWaterfall.sol#108-119):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:124: Reentrancy in FeeWaterfall.pushBasketReserve(uint256) (src/FeeWaterfall.sol#124-134):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:138: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#138-148):\n[medium/medium] divide-before-multiply at src/FeeHook.sol:239: FeeHook._collect(address,uint256,FeeHook.FeeQuote,bool) (src/FeeHook.sol#239-252) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/RebalanceExecutor.sol:279: RebalanceExecutor._requireWindow() (src/RebalanceExecutor.sol#279-291) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/RebalanceExecutor.sol:303: RebalanceExecutor._target(address,uint256) (src/RebalanceExecutor.sol#303-307) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/TimelockedAdmin.sol:153: TimelockedAdmin.cancel(bytes32) (src/TimelockedAdmin.sol#153-160) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/FeeWaterfall.sol:213: FeeWaterfall._distribute() (src/FeeWaterfall.sol#213-236) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:222: EpochManager.clearExpired() (src/EpochManager.sol#222-226) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IndexVault.sol:326: IndexVault._update(address,address,uint256) (src/IndexVault.sol#326-332) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:215: EpochManager.cancelPending() (src/EpochManager.sol#215-219) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IndexVault.sol:116: IndexVault.deposit(uint256,address,uint256) (src/IndexVault.sol#116-150) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:303: EpochManager.basketStale() (src/EpochManager.sol#303-307) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/RebalanceExecutor.sol:219: RebalanceExecutor._authorize(address,address,uint256) (src/RebalanceExecutor.sol#219-277) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/RebalanceExecutor.sol:105: Reentrancy in RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#105-142):\n[medium/medium] uninitialized-local at src/RebalanceExecutor.sol:228: RebalanceExecutor._authorize(address,address,uint256).exit (src/RebalanceExecutor.sol#228) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:335: EpochManager._checkBasket(EpochManager.Proposal).total (src/EpochManager.sol#335) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:220: FeeWaterfall._distribute().protocol (src/FeeWaterfall.sol#220) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:336: EpochManager._checkBasket(EpochManager.Proposal).additions (src/EpochManager.sol#336) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:373: EpochManager._checkQuorum(bytes32,bytes[]).last (src/EpochManager.sol#373) is a local variable never initialized\n[medium/medium] uninitialized-local at src/RebalanceExecutor.sol:325: RebalanceExecutor._nav().value (src/RebalanceExecutor.sol#325) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:219: FeeWaterfall._distribute().swarm (src/FeeWaterfall.sol#219) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IndexVault.sol:347: IndexVault._nav().tokenValue (src/IndexVault.sol#347) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:221: FeeWaterfall._distribute().utility (src/FeeWaterfall.sol#221) is a local variable never initialized\n[medium/medium] unused-return at src/RebalanceExecutor.sol:309: RebalanceExecutor._valueOf(address) (src/RebalanceExecutor.sol#309-314) ignores return value by (value,None) = registry.convert(token,balance,reserve) (src/RebalanceExecutor.sol#313)\n[medium/medium] unused-return at src/RebalanceExecutor.sol:219: RebalanceExecutor._authorize(address,address,uint256) (src/RebalanceExecutor.sol#219-277) ignores return value by (tradeValue,None) = registry.convert(sellToken,sellAmount,reserve) (src/RebalanceExecutor.sol#259)\n[low/medium] events-access at src/TimelockedAdmin.sol:188: TimelockedAdmin.setGuardian(address) (src/TimelockedAdmin.sol#188-193) should emit an event for: \n[low/medium] missing-zero-check at src/FeeHookDeployer.sol:50: FeeHookDeployer.deploy(bytes32).deployed (src/FeeHookDeployer.sol#50) lacks a zero-check on :\n[low/medium] missing-zero-check at src/AssetRegistry.sol:161: AssetRegistry.setReserveFeed(address,uint32).feed (src/AssetRegistry.sol#161) lacks a zero-check on :\n[low/medium] missing-zero-check at src/FeeWaterfall.sol:87: FeeWaterfall.constructor(address,address,address,address).weth_ (src/FeeWaterfall.sol#87) lacks a zero-check on :\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: registry.isQuarantined(held[i]) (src/RebalanceExecutor.sol#322)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/IndexVault.sol:274: IndexVault.holdings() (src/IndexVault.sol#274-298) has external calls inside a loop: (values[i + 1],priced[i + 1]) = registry.convert(_held[i],balances[i + 1],asset) (src/IndexVault.sol#295)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#348)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: (ok,ret) = held[i].staticcall{gas: BALANCE_GAS}(abi.encodeCall(IERC20.balanceOf,(address(vault)))) (src/RebalanceExecutor.sol#323-324)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: registry.isQuarantined(token) (src/IndexVault.sol#343)\n[low/medium] calls-loop at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-212) has external calls inside a loop: ! registry.isEligible(token) (src/EpochManager.sol#197)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: registry.isQuarantined(token) (src/IndexVault.sol#343)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: (ok,ret) = held[i].staticcall{gas: BALANCE_GAS}(abi.encodeCall(IERC20.balanceOf,(address(vault)))) (src/RebalanceExecutor.sol#323-324)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#348)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: registry.isQuarantined(held[i]) (src/RebalanceExecutor.sol#322)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: registry.isQuarantined(token) (src/IndexVault.sol#343)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: (value,ok) = registry.convert(held[i],balance,reserve) (src/RebalanceExecutor.sol#329)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/EpochManager.sol:325: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#325-355) has external calls inside a loop: ! registry.isEligible(token) (src/EpochManager.sol#343)\n[low/medium] calls-loop at src/EpochManager.sol:367: EpochManager._checkQuorum(bytes32,bytes[]) (src/EpochManager.sol#367-381) has external calls inside a loop: admin.roleOf(signer) != Role.Signer (src/EpochManager.sol#377)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] calls-loop at src/EpochManager.sol:325: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#325-355) has external calls inside a loop: weight > registry.maxWeightBps(token) (src/EpochManager.sol#346)\n[low/medium] calls-loop at src/IndexVault.sol:362: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#362-367) has external calls inside a loop: (ok,ret) = token.call{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.transfer,(to,amount))) (src/IndexVault.sol#363)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#348)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#348)\n[low/medium] calls-loop at src/IndexVault.sol:336: IndexVault._nav() (src/IndexVault.sol#336-352) has external calls inside a loop: registry.isQuarantined(token) (src/IndexVault.sol#343)\n[low/medium] calls-loop at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-212) has external calls inside a loop: q.weightsBps[i] > registry.maxWeightBps(token) (src/EpochManager.sol#198)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) has external calls inside a loop: (value,ok) = registry.convert(held[i],balance,reserve) (src/RebalanceExecutor.sol#329)\n[low/medium] calls-loop at src/EpochManager.sol:367: EpochManager._checkQuorum(bytes32,bytes[]) (src/EpochManager.sol#367-381) has external calls inside a loop: admin.roleOf(signer) != Role.Signer (src/EpochManager.sol#377)\n[low/medium] calls-loop at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#357-358)\n[low/medium] reentrancy-benign at src/FeeWaterfall.sol:213: Reentrancy in FeeWaterfall._distribute() (src/FeeWaterfall.sol#213-236):\n[low/medium] reentrancy-benign at src/FeeWaterfall.sol:138: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#138-148):\n[low/medium] reentrancy-events at src/TimelockedAdmin.sol:126: Reentrancy in TimelockedAdmin.execute(address,uint256,bytes,bytes32) (src/TimelockedAdmin.sol#126-149):\n[low/medium] reentrancy-events at src/FeeWaterfall.sol:138: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#138-148):\n[low/medium] reentrancy-events at src/FeeWaterfall.sol:213: Reentrancy in FeeWaterfall._distribute() (src/FeeWaterfall.sol#213-236):\n[low/medium] return-bomb at src/RebalanceExecutor.sol:338: RebalanceExecutor._balanceOf(address) (src/RebalanceExecutor.sol#338-343) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] return-bomb at src/RebalanceExecutor.sol:318: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#318-336) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] return-bomb at src/IndexVault.sol:362: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#362-367) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] return-bomb at src/IndexVault.sol:356: IndexVault._balanceOf(address) (src/IndexVault.sol#356-360) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-212) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:215: EpochManager.cancelPending() (src/EpochManager.sol#215-219) uses timestamp for comparisons\n[low/medium] timestamp at src/TimelockedAdmin.sol:153: TimelockedAdmin.cancel(bytes32) (src/TimelockedAdmin.sol#153-160) uses timestamp for comparisons\n[low/medium] timestamp at src/TimelockedAdmin.sol:126: TimelockedAdmin.execute(address,uint256,bytes,bytes32) (src/TimelockedAdmin.sol#126-149) uses timestamp for comparisons\n[low/medium] timestamp at src/AssetRegistry.sol:114: AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32) (src/AssetRegistry.sol#114-143) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:222: EpochManager.clearExpired() (src/EpochManager.sol#222-226) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:316: EpochManager._checkTiming(EpochManager.Proposal,uint256) (src/EpochManager.sol#316-323) uses timestamp for comparisons\n[low/medium] timestamp at src/RebalanceExecutor.sol:279: RebalanceExecutor._requireWindow() (src/RebalanceExecutor.sol#279-291) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:325: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#325-355) uses timestamp for comparisons\n[low/medium] timestamp at src/AssetRegistry.sol:318: AssetRegistry._feedPrice(address,uint8,uint32) (src/AssetRegistry.sol#318-335) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:137: EpochManager.publis","passed":true},{"durationMs":1707,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/FeeHookDeployer.sol:64: `abi.encodePacked()` Hash Collision\n[high] reentrancy-state-change at src/AssetRegistry.sol:128: Reentrancy: State change after external call (29 places)\n[high] strict-equality-contract-balance at src/FeeWaterfall.sol:214: Dangerous strict equality checks on contract balances\n[high] unprotected-initializer at src/FeeHookDeployer.sol:62: Unprotected initializer\n[high] unsafe-casting at src/EpochManager.sol:159: Unsafe Casting of integers (2 places)\n[low] costly-loop at src/IndexVault.sol:211: Costly operations inside loop\n[low] large-numeric-literal at src/AssetRegistry.sol:27: Large Numeric Literal (14 places)\n[low] literal-instead-of-constant at src/AssetRegistry.sol:86: Literal Instead of Constant (33 places)\n[low] missing-inheritance at src/AssetRegistry.sol:14: Missing Inheritance (3 places)\n[low] non-reentrant-not-first at src/FeeWaterfall.sol:165: `nonReentrant` is Not the First Modifier\n[low] require-revert-in-loop at src/EpochManager.sol:195: Loop Contains `require`/`revert` (6 places)\n[low] state-no-address-check at src/AssetRegistry.sol:163: Address State Variable Set Without Checks\n[low] unchecked-return at src/FeeWaterfall.sol:112: Unchecked Return (3 places)\n[low] uninitialized-local-variable at src/EpochManager.sol:195: Uninitialized Local Variable (10 places)\n[low] unsafe-erc20-operation at src/IndexVault.sol:363: Unsafe ERC20 Operation\n[low] unused-public-function at src/FeeHookDeployer.sol:58: Public Function Not Used Internally\n[low] unused-state-variable at src/IndexVault.sol:29: Unused State Variable (2 places)","passed":true},{"durationMs":7689,"exitCode":0,"name":"proof 55418307b794","output":"Compiling 76 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.89s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-b6a008c7/Proof_55418307b794.t.sol:GuardianVetoTest\n[PASS] test_timelockCanLiftGuardianPauseDespiteVeto() (gas: 217890)\n[PASS] test_timelockCanReplaceGuardianDespiteVeto() (gas: 187040)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.75ms (812.75µs CPU time)\n\nRan 1 test suite in 10.12ms (1.75ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true},{"durationMs":5544,"exitCode":0,"name":"proof 15f0a7138f3e","output":"2026-10-06T18:00:03.233415Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-s03Hfo/repo/test/imd-proof-b6a008c7/Proof_55418307b794.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.22s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-43951ac1/Proof_15f0a7138f3e.t.sol:StrictRedemptionProof\n[PASS] test_strictRedemptionMustPreserveClaimOnUnreadableAsset() (gas: 1675926)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.72ms (3.35ms CPU time)\n\nRan 1 test suite in 34.12ms (3.72ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":5037,"exitCode":0,"name":"proof 6e42c1cfca29","output":"2026-10-06T18:00:08.098642Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-s03Hfo/repo/test/imd-proof-43951ac1/Proof_15f0a7138f3e.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.41s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-48e8df35/Proof_6e42c1cfca29.t.sol:StaleActivationProof\n[PASS] test_expiryMustNotOverrideSnapshotFreshness() (gas: 1140467)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.66ms (2.44ms CPU time)\n\nRan 1 test suite in 10.06ms (2.66ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"61d4968ffe0afdfc5c1ea11a495540d561c50f5199fbac60e5be9dfd227f1cb3","verifiedTreeHash":"99b9c6beb0326a2f1361807c502c1d470e1e5420","verifierVersion":"0.1.0+94826a22"},{"checks":[{"durationMs":10983,"exitCode":0,"name":"build","output":"Compiling 119 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.82s\nCompiler run successful!\n","passed":true},{"durationMs":14804,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 101781)\n[PASS] test_adminHandoverIsTwoStep() (gas: 479772)\n[PASS] test_adminWalletCannotHoldARole() (gas: 100455)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168733)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 135721)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 102197)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 96614)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 286414)\n[PASS] test_executorMustBeAContract() (gas: 111456)\n[PASS] test_guardianCanOnlyTighten() (gas: 257021)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 152176)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 394474)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 104869)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 121213)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 449040)\n[PASS] test_quorumBounds() (gas: 99422)\n[PASS] test_quorumCannotExceedSigners() (gas: 101554)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68212)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 156281)\n[PASS] test_signerCannotBecomeGuardian() (gas: 116638)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 479369)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 124478)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 15.04ms (10.38ms CPU time)\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85303, ~: 85570)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 18.30ms (17.82ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 69624, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150695)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 755359)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 112835)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 112721)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 856978)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 363941)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167465)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178522)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23672)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41472)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 507113)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 105042)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 554220)\n[PASS] test_paramsStayInsideHardBounds() (gas: 465592)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53223)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177708)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 399044)\n[PASS] test_routerAllowlist() (gas: 393268)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 104844)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1619006)\n[PASS] test_windowCannotExceedInterval() (gas: 107512)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 28.28ms (30.63ms CPU time)\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 422673, ~: 428234)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 731712)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 874651)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1318135)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 417433)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 784499)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1515982)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94400)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 54.94ms (50.10ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 31335916)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 31381499)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 31453042)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 59043629)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 76.10ms (66.91ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384080, ~: 382102)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 377799)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357107)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114041)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 53713275)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 244667)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 694366)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 368843)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157062)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1130339)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809938)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 348708)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 430596)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 115.96ms (178.32ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384451, ~: 384852)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 371511)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352128)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114711)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 20120309)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 238127)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 689352)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 362642)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157561)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1131157)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810808)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 354088)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 425628)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 119.04ms (111.55ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 318428, ~: 313034)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 322563)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301575)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 113969)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 68310380)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 211662)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 648459)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 311553)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 968407)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1074474)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809854)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 328092)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 286431)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 400973)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 218.14ms (210.31ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 437773, ~: 437292)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 3858103)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134169)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3055117)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 2907520)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2874975)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 524862)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 712862)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 472042)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 385649)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85381)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2843910)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1143353)\n[PASS] test_onlyKeepersTrade() (gas: 285286)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3221205)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 328771)\n[PASS] test_pauseStopsTrading() (gas: 130758)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3585737)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3558374)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5306886)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3085273)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 988313)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3607411)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 374560)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4111623)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4138694)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3806620)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4646668)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 220.04ms (330.30ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1239828)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1415199)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1840734)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 977294)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798328)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67786)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338961)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964187)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199928)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1254774)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289617)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 288158)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216724)\n[PASS] test_rejectsBelowQuorum() (gas: 275939)\n[PASS] test_rejectsExpiredProposal() (gas: 149842)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694540)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150458)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513564)\n[PASS] test_rejectsMismatchedArrays() (gas: 469390)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163373)\n[PASS] test_rejectsQuarantinedToken() (gas: 243300)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807432)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289634)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859276)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 287014)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1691479)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1134972)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192219)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368930)\n[PASS] test_rejectsWrongEpoch() (gas: 191179)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157179)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508291)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496134)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 873664)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1327644)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 290156)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 656858)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 980676)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2291230)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2279650)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 230.94ms (70.96ms CPU time)\n\nRan 16 tests for test/AdversarialAccounting.t.sol:AdversarialAccountingTest\n[PASS] testFuzz_failedReservePayoutRestoresBurnAndBalances(uint8,bool) (runs: 1000, μ: 426748, ~: 426897)\n[PASS] testFuzz_invalidReserveReturnRollsBackDeposit(uint8) (runs: 1000, μ: 385066, ~: 385125)\n[PASS] testFuzz_strictHeldTransferFailureRollsBackEarlierPayouts(uint8) (runs: 1000, μ: 2770777, ~: 2770746)\n[PASS] testFuzz_taxedDepositCreditsOnlyReceivedAssets(uint256,uint16) (runs: 1000, μ: 252865, ~: 258148)\n[PASS] test_claimCallbackCannotDistributeOrClaimTwice() (gas: 521648)\n[PASS] test_depositCallbackCannotMintTwice() (gas: 405425)\n[PASS] test_depositForAnotherReceiverDoesNotGiveCallerRedemptionAuthority() (gas: 294039)\n[PASS] test_explicitNonStrictExitPaysHealthyTokensAfterAFailedToken() (gas: 2776538)\n[PASS] test_failedBasketPushRestoresBucketsSupplyAndAllowance() (gas: 734608)\n[PASS] test_failedClaimRollsBackItsImplicitDistributionAndCanBeRetried() (gas: 721526)\n[PASS] test_maximumConfiguredCapCanBeDepositedAndRedeemed() (gas: 353990)\n[PASS] test_noReturnHeldTokenPaysThePreviewedSlice() (gas: 1551542)\n[PASS] test_noReturnReserveWorksForDepositRedemptionAndFeeClaims() (gas: 602142)\n[PASS] test_oneUnitDepositExitAndReentryDoesNotCreateValue() (gas: 391985)\n[PASS] test_redemptionCallbackCannotBurnAnotherHoldersShares() (gas: 476240)\n[PASS] test_taxedDepositCapUsesNetReceiptsAndRevertsAtomicallyAboveCap() (gas: 684386)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 347.34ms (791.81ms CPU time)\n\nRan 9 tests for test/ProposalTransitions.t.sol:ProposalTransitionsTest\n[PASS] testFuzz_eachAttestedFieldIsBoundByTheSignatures(uint8,uint64) (runs: 1000, μ: 611692, ~: 611676)\n[PASS] test_badSignatureDoesNotConsumeHashOrClearExpiredPending() (gas: 1082526)\n[PASS] test_clearExpiredCannotConsumeAtExpiryAndCannotClearTwice() (gas: 549643)\n[PASS] test_pendingAgeRuleChangePreventsActivation() (gas: 1112484)\n[PASS] test_pendingCapReductionPreventsActivation() (gas: 1149515)\n[PASS] test_pendingMethodologyChangePreventsActivation() (gas: 1124020)\n[PASS] test_pendingRevocationPreventsActivation() (gas: 1108576)\n[PASS] test_reportSignatureCannotAuthorizeABasketAndViceVersa() (gas: 368441)\n[PASS] test_signaturesCannotBeReplayedOnAnotherEpochManager() (gas: 601249)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 397.41ms (399.71ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5595057, ~: 5603992)\n[PASS] test_depositCapBoundsTheBeta() (gas: 906482)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 841151)\n[PASS] test_depositHonoursMinShares() (gas: 380533)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4788527)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185781)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 31450739)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 653450)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 431084)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 303147)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4471639)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4687815)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 489895)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 504688)\n[PASS] test_redeemPaysProRataInKind() (gas: 4705882)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 351729)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4669386)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 4936359)\n[PASS] test_shareTokenMetadata() (gas: 36939)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 4856603)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4570054)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154817)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 452.37ms (531.81ms CPU time)\n\nRan 2 tests for test/invariant/TimelockCustody.invariant.t.sol:TimelockCustodyInvariantTest\n[PASS] invariant_treasuryFundsMoveOnlyThroughReadyUnvetoedOperations() (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------+----------+-------+---------+----------╮\n| Contract        | Selector | Calls | Reverts | Discards |\n+=========================================================+\n| TreasuryHandler | advance  | 3265  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | cancel   | 3253  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | execute  | 3245  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | fund     | 3368  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | schedule | 3253  | 0       | 0        |\n╰-----------------+----------+-------+---------+----------╯\n\n[PASS] test_handlerReachesEarlyUnauthorizedExpiredVetoedAndSuccessfulPayments() (gas: 1101659)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.25s (1.25s CPU time)\n\nRan 2 tests for test/invariant/LaunchLedger.invariant.t.sol:LaunchLedgerInvariantTest\n[PASS] invariant_fixedSupplyBalancesAndAllowancesMatchLedger() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+-------------------+-------+---------+----------╮\n| Contract            | Selector          | Calls | Reverts | Discards |\n+======================================================================+\n| LaunchLedgerHandler | approve           | 4069  | 0       | 0        |\n|---------------------+-------------------+-------+---------+----------|\n| LaunchLedgerHandler | rejectOverBalance | 4076  | 0       | 0        |\n|---------------------+-------------------+-------+---------+----------|\n| LaunchLedgerHandler | spend             | 4154  | 0       | 0        |\n|---------------------+-------------------+-------+---------+----------|\n| LaunchLedgerHandler | transfer          | 4085  | 0       | 0        |\n╰---------------------+-------------------+-------+---------+----------╯\n\n[PASS] test_zeroSelfFullSupplyAndInfiniteAllowanceSequences() (gas: 611173)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.07s (2.07s CPU time)\n\nRan 2 tests for test/invariant/ReserveAccounting.invariant.t.sol:ReserveAccountingInvariantTest\n[PASS]\nReserveAccountingInvariantTest invariants:\n[PASS] invariant_allReserveRemainsInTheClosedSetOfAccounts\n[PASS] invariant_pendingDistributedAndPaidFeesConserveEveryUnit\n[PASS] invariant_vaultFlowsAndSharesMatchIndependentGhosts\n ReserveAccountingInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------------+----------------+-------+---------+----------╮\n| Contract                 | Selector       | Calls | Reverts | Discards |\n+========================================================================+\n| ReserveAccountingHandler | claim          | 1520  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | collectFees    | 1545  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | deposit        | 1527  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | distribute     | 1433  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | donate         | 1417  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | pause          | 1485  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | pushBasket     | 1486  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | redeem         | 1514  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | redeemTreasury | 1406  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | roundTrip      | 1494  | 0       | 0        |\n|--------------------------+----------------+-------+---------+----------|\n| ReserveAccountingHandler | transferShares | 1557  | 0       | 0        |\n╰--------------------------+----------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesPendingFeesPausesTransfersAndTreasuryExit() (gas: 2243265)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.22s (4.22s CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 2293  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 2362  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 2324  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 2297  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 2322  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 2392  | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 2394  | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.63s (14.62s CPU time)\n\nRan 17 test suites in 14.64s (24.47s CPU time): 246 tests passed, 0 failed, 0 skipped (246 total tests)\n","passed":true},{"durationMs":98,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":262,\"docs/METHODOLOGY.md\":132,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":76,\"foundry.toml\":34,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":320,\"src/EpochManager.sol\":377,\"src/FeeHook.sol\":246,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":239,\"src/IndexVault.sol\":338,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":323,\"src/TimelockedAdmin.sol\":262,\"src/interfaces/IIndex.sol\":87,\"test/ADVERSARIAL_COVERAGE.md\":59,\"test/AdversarialAccounting.t.sol\":264,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":560,\"test/FeeHook.t.sol\":396,\"test/FeeWaterfall.t.sol\":235,\"test/IndexVault.t.sol\":365,\"test/LaunchToken.t.sol\":69,\"test/ProposalTransitions.t.sol\":160,\"test/RebalanceExecutor.t.sol\":487,\"test/TimelockedAdmin.t.sol\":272,\"test/invariant/Handler.sol\":163,\"test/invariant/LaunchLedger.invariant.t.sol\":110,\"test/invariant/ReserveAccounting.invariant.t.sol\":312,\"test/invariant/Solvency.invariant.t.sol\":98,\"test/invariant/TimelockCustody.invariant.t.sol\":167,\"test/utils/AdversarialToken.sol\":86,\"test/utils/Fixture.sol\":279,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"940f9713e241c84880794329bd0353c000a49bc55fd90d6d926b5f16b328924d","verifiedTreeHash":"04ddacef66a0277c41ac4d6ecb8943d102e67ce4","verifierVersion":"0.1.0+e6140b7a"},{"checks":[{"durationMs":14222,"exitCode":0,"name":"build","output":"Compiling 114 files with Solc 0.8.26\nSolc 0.8.26 finished in 14.03s\nCompiler run successful!\n","passed":true},{"durationMs":2964,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85046, ~: 85564)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.72ms (6.96ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 385871, ~: 387327)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 380397)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357085)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114108)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25733539)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 247265)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 703892)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 371441)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162204)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1185543)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809916)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 752656)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 292962)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 351230)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 436368)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 80.58ms (88.18ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 453086, ~: 452980)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 4023605)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134150)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3184386)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 3035744)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2996731)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 533159)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 738772)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 487730)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 401293)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85362)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2965570)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1152550)\n[PASS] test_onlyKeepersTrade() (gas: 285301)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3243973)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 344462)\n[PASS] test_pauseStopsTrading() (gas: 130761)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3704159)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3680033)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5470643)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3237194)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 1024845)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3768650)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 390207)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4268519)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4295590)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3905897)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4799416)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 79.71ms (185.55ms CPU time)\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 422917, ~: 428199)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 832760)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 928119)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1329292)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 422507)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 789148)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1518932)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94422)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 80.78ms (67.86ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 385716, ~: 383614)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 374122)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352194)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114689)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25638654)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 240738)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 698941)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 365253)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162729)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1186286)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810786)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 746181)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 299243)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 356623)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 431410)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 80.83ms (100.07ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 32922360)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 32968008)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 33039419)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 62564150)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 119.66ms (117.68ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 68784, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150827)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 759986)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 117462)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 117348)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 866232)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 364271)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167510)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178546)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23738)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41428)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 514624)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 109669)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 583244)\n[PASS] test_paramsStayInsideHardBounds() (gas: 473110)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53355)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177818)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 402781)\n[PASS] test_routerAllowlist() (gas: 396934)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 109471)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1623567)\n[PASS] test_windowCannotExceedInterval() (gas: 112139)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 119.77ms (35.69ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 320462, ~: 325457)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 325161)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301553)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114036)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 166127646)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 214260)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 657984)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 314151)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 973549)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1129678)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809832)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 330690)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 638324)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 242556)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 288953)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 406745)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 193.51ms (207.19ms CPU time)\n\nRan 26 tests for test/Revision.t.sol:RevisionTest\n[PASS] test_activationAtSnapshotAgeBoundarySucceeds() (gas: 1196785)\n[PASS] test_activationRechecksSnapshotAgeWithFreshPrices() (gas: 865609)\n[PASS] test_adminCanCancelRecoveryAndGuardianStillVetoesOtherCalls() (gas: 329121)\n[PASS] test_autoQuarantineDoesNotVetoProposalOrCloseFairDeposits() (gas: 3891522)\n[PASS] test_autoQuarantineRetainsDeltaAndWindowUntilGuardianConfirms() (gas: 6751411)\n[PASS] test_deadUntransferablePositionPreservesClaimsAndClosesDeposits() (gas: 5557737)\n[PASS] test_dustDepositCannotLockExistingShares() (gas: 5352326)\n[PASS] test_emptyBasketCannotResetTurnoverHistory() (gas: 1836695)\n[PASS] test_firstDepositChecksRegistryReserveScale() (gas: 157196)\n[PASS] test_firstTradeCannotChooseWindowStart() (gas: 1827439)\n[PASS] test_fullDustExitCanFreeHeldSlot() (gas: 5073260)\n[PASS] test_gasBurningUnquarantinedBalanceFailsWithBoundedError() (gas: 5596809)\n[PASS] test_guardianCannotVetoReplacementOrUnpause() (gas: 881301)\n[PASS] test_heldQuarantineClosesDepositsEvenWithFreshFeed() (gas: 4626889)\n[PASS] test_keeperFailuresCannotCreateExitRightsForAnyMember() (gas: 13567352)\n[PASS] test_lateFirstTradeUsesTheActivationSchedule() (gas: 2526980)\n[PASS] test_newSharesCannotRedeemInTheirDepositBlock() (gas: 4967089)\n[PASS] test_newSharesCannotTransferOrTransferFromToBypassLock() (gas: 4757506)\n[PASS] test_onlyKeeperOrTimelockChoosesTreasuryDepositTime() (gas: 504557)\n[PASS] test_proposalDelayAndSnapshotAgeMustLeaveExecutionTime() (gas: 1322352)\n[PASS] test_proposalDelayCannotConsumeEntireValidityPeriod() (gas: 33021)\n[PASS] test_quarantinedGasBurnerCannotStopUnrelatedTrades() (gas: 5283723)\n[PASS] test_releaseStartsANewFailureStreak() (gas: 6550330)\n[PASS] test_strictUnreadableBalancePreservesSharesAfterHoldingPeriod() (gas: 4506318)\n[PASS] test_unreadableNonStrictRedemptionEmitsUnknownSkippedSlice() (gas: 4549019)\n[PASS] test_untrackedDonationDoesNotBlockMemberPurchase() (gas: 1968485)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 399.29ms (184.23ms CPU time)\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 106978)\n[PASS] test_adminHandoverIsTwoStep() (gas: 483912)\n[PASS] test_adminWalletCannotHoldARole() (gas: 105652)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168689)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 140963)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 107461)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 98982)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 291612)\n[PASS] test_executorMustBeAContract() (gas: 116653)\n[PASS] test_guardianCanOnlyTighten() (gas: 256933)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 157844)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 398614)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 110044)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 124107)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 472150)\n[PASS] test_quorumBounds() (gas: 104597)\n[PASS] test_quorumCannotExceedSigners() (gas: 106729)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68190)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 159131)\n[PASS] test_signerCannotBecomeGuardian() (gas: 144280)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 487684)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 129127)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 399.38ms (6.07ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1242734)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1418116)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1843795)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 980485)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798269)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67665)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338670)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964025)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199917)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1257669)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289433)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 287974)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216384)\n[PASS] test_rejectsBelowQuorum() (gas: 275755)\n[PASS] test_rejectsExpiredProposal() (gas: 149875)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694378)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150491)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513185)\n[PASS] test_rejectsMismatchedArrays() (gas: 469498)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163409)\n[PASS] test_rejectsQuarantinedToken() (gas: 243222)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807292)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289450)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859202)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 286896)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1696120)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1146000)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192211)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368746)\n[PASS] test_rejectsWrongEpoch() (gas: 191234)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157190)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508033)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496202)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 879267)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1330561)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 289972)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 689091)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 983533)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2298890)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2285681)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 400.65ms (108.39ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5833084, ~: 5845480)\n[PASS] test_depositCapBoundsTheBeta() (gas: 1002963)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 938312)\n[PASS] test_depositHonoursMinShares() (gas: 428641)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4976759)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185649)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 33037112)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 749946)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 478022)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 350195)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4694172)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4870581)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 536811)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 553925)\n[PASS] test_redeemPaysProRataInKind() (gas: 4879947)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 401017)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4849001)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 5108067)\n[PASS] test_shareTokenMetadata() (gas: 36983)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 5047402)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4746454)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154791)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 400.83ms (533.17ms CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 32, calls: 2048, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 293   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 293   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 293   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 290   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 314   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 293   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 272   | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.78s (2.75s CPU time)\n\nRan 13 test suites in 2.78s (5.14s CPU time): 247 tests passed, 0 failed, 0 skipped (247 total tests)\n","passed":true},{"durationMs":163,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":287,\"docs/METHODOLOGY.md\":139,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":95,\"foundry.toml\":34,\"launch.json\":71,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":336,\"src/EpochManager.sol\":382,\"src/FeeHook.sol\":258,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":242,\"src/IndexVault.sol\":368,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":344,\"src/TimelockedAdmin.sol\":271,\"src/interfaces/IIndex.sol\":91,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":560,\"test/FeeHook.t.sol\":433,\"test/FeeWaterfall.t.sol\":239,\"test/IndexVault.t.sol\":366,\"test/LaunchToken.t.sol\":69,\"test/RebalanceExecutor.t.sol\":488,\"test/Revision.t.sol\":426,\"test/TimelockedAdmin.t.sol\":272,\"test/invariant/Handler.sol\":165,\"test/invariant/Solvency.invariant.t.sol\":95,\"test/utils/Fixture.sol\":282,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b8b25c0f4f6590d018965896349a81ad11921a3c33f18874bf881568ae096316","verifiedTreeHash":"8ba13686af83ac63e6db21d3e51780398c13fbc2","verifierVersion":"0.1.0+94826a22"},{"checks":[{"durationMs":21526,"exitCode":0,"name":"build","output":"Compiling 113 files with Solc 0.8.26\nSolc 0.8.26 finished in 21.24s\nCompiler run successful!\n","passed":true},{"durationMs":4658,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 423589, ~: 428192)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 731712)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 874651)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1318135)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 417433)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 784499)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1515982)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94400)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 81.92ms (85.22ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 318414, ~: 313034)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 322563)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301575)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 113969)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 68310380)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 211662)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 648459)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 311553)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 968407)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1074474)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809854)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 328092)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 286431)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 400973)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 163.31ms (252.05ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 31335916)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 31381499)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 31453042)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 59043629)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 189.84ms (158.77ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384172, ~: 382102)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 377799)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357107)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114041)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 53713275)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 244667)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 694366)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 368843)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157062)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1130339)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809938)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 348708)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 430596)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 189.88ms (270.77ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384793, ~: 384882)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 371511)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352128)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114711)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 20120309)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 238127)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 689352)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 362642)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157561)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1131157)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810808)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 354088)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 425628)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 189.93ms (188.62ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 437818, ~: 437292)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 3858103)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134169)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3055117)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 2907520)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2874975)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 524862)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 712862)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 472042)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 385649)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85381)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2843910)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1143353)\n[PASS] test_onlyKeepersTrade() (gas: 285286)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3221205)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 328771)\n[PASS] test_pauseStopsTrading() (gas: 130758)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3585737)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3558374)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5306886)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3085273)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 988313)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3607411)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 374560)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4111623)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4138694)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3806620)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4646668)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 191.14ms (363.13ms CPU time)\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 101781)\n[PASS] test_adminHandoverIsTwoStep() (gas: 479772)\n[PASS] test_adminWalletCannotHoldARole() (gas: 100455)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168733)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 135721)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 102197)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 96614)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 286414)\n[PASS] test_executorMustBeAContract() (gas: 111456)\n[PASS] test_guardianCanOnlyTighten() (gas: 257021)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 152176)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 394474)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 104869)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 121213)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 449040)\n[PASS] test_quorumBounds() (gas: 99422)\n[PASS] test_quorumCannotExceedSigners() (gas: 101554)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68212)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 156281)\n[PASS] test_signerCannotBecomeGuardian() (gas: 116638)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 479369)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 124478)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 590.51ms (15.38ms CPU time)\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85134, ~: 85588)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 591.09ms (592.87ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 65912, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150695)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 755359)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 112835)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 112721)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 856978)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 363941)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167465)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178522)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23672)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41472)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 507113)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 105042)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 554220)\n[PASS] test_paramsStayInsideHardBounds() (gas: 465592)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53223)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177708)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 399044)\n[PASS] test_routerAllowlist() (gas: 393268)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 104844)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1619006)\n[PASS] test_windowCannotExceedInterval() (gas: 107512)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 591.24ms (162.68ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1239828)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1415071)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1840734)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 977294)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798328)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67786)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338961)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964187)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199928)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1254774)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289617)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 288158)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216724)\n[PASS] test_rejectsBelowQuorum() (gas: 275939)\n[PASS] test_rejectsExpiredProposal() (gas: 149842)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694540)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150458)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513564)\n[PASS] test_rejectsMismatchedArrays() (gas: 469390)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163373)\n[PASS] test_rejectsQuarantinedToken() (gas: 243300)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807432)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289634)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859276)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 287014)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1691479)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1134972)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192219)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368930)\n[PASS] test_rejectsWrongEpoch() (gas: 191179)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157179)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508291)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496134)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 873664)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1327644)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 290156)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 656858)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 980676)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2291230)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2279650)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 591.31ms (149.43ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5595799, ~: 5603834)\n[PASS] test_depositCapBoundsTheBeta() (gas: 906482)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 841151)\n[PASS] test_depositHonoursMinShares() (gas: 380533)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4788527)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185781)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 31450739)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 653450)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 431084)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 303147)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4471639)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4687815)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 489895)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 504688)\n[PASS] test_redeemPaysProRataInKind() (gas: 4705882)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 351729)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4669386)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 4936359)\n[PASS] test_shareTokenMetadata() (gas: 36939)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 4856603)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4570054)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154817)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 627.30ms (810.51ms CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 32, calls: 2048, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 281   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 315   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 295   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 291   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 291   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 274   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 301   | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.24s (4.20s CPU time)\n\nRan 12 test suites in 4.25s (8.24s CPU time): 215 tests passed, 0 failed, 0 skipped (215 total tests)\n","passed":true},{"durationMs":256,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":262,\"docs/METHODOLOGY.md\":132,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":76,\"foundry.toml\":34,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":320,\"src/EpochManager.sol\":377,\"src/FeeHook.sol\":246,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":239,\"src/IndexVault.sol\":338,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":323,\"src/TimelockedAdmin.sol\":262,\"src/interfaces/IIndex.sol\":87,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":558,\"test/FeeHook.t.sol\":396,\"test/FeeWaterfall.t.sol\":235,\"test/IndexVault.t.sol\":365,\"test/LaunchToken.t.sol\":69,\"test/RebalanceExecutor.t.sol\":487,\"test/TimelockedAdmin.t.sol\":272,\"test/invariant/Handler.sol\":163,\"test/invariant/Solvency.invariant.t.sol\":95,\"test/utils/Fixture.sol\":279,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":8589,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/RebalanceExecutor.sol:166: Reentrancy in RebalanceExecutor._swap(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#166-193):\n[high/medium] reentrancy-balance at src/RebalanceExecutor.sol:166: Reentrancy in RebalanceExecutor._swap(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#166-193):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:122: Reentrancy in FeeWaterfall.pushBasketReserve(uint256) (src/FeeWaterfall.sol#122-131):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:135: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#135-145):\n[high/medium] reentrancy-eth at src/FeeWaterfall.sol:107: Reentrancy in FeeWaterfall.claim(FeeWaterfall.Bucket) (src/FeeWaterfall.sol#107-118):\n[medium/medium] divide-before-multiply at src/FeeHook.sol:227: FeeHook._collect(address,uint256,FeeHook.FeeQuote,bool) (src/FeeHook.sol#227-240) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/RebalanceExecutor.sol:292: RebalanceExecutor._target(address,uint256) (src/RebalanceExecutor.sol#292-296) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/IndexVault.sol:332: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#332-337) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/RebalanceExecutor.sol:208: RebalanceExecutor._authorize(address,address,uint256) (src/RebalanceExecutor.sol#208-266) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/RebalanceExecutor.sol:298: RebalanceExecutor._valueOf(address) (src/RebalanceExecutor.sol#298-302) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IndexVault.sol:332: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#332-337) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:298: EpochManager.basketStale() (src/EpochManager.sol#298-302) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:214: EpochManager.cancelPending() (src/EpochManager.sol#214-218) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/FeeWaterfall.sol:210: FeeWaterfall._distribute() (src/FeeWaterfall.sol#210-233) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/EpochManager.sol:221: EpochManager.clearExpired() (src/EpochManager.sol#221-225) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IndexVault.sol:146: IndexVault.redeem(uint256,address,bool) (src/IndexVault.sol#146-171) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/TimelockedAdmin.sol:146: TimelockedAdmin.cancel(bytes32) (src/TimelockedAdmin.sol#146-151) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IndexVault.sol:110: IndexVault.deposit(uint256,address,uint256) (src/IndexVault.sol#110-138) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/RebalanceExecutor.sol:217: RebalanceExecutor._authorize(address,address,uint256).exit (src/RebalanceExecutor.sol#217) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:330: EpochManager._checkBasket(EpochManager.Proposal).total (src/EpochManager.sol#330) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:217: FeeWaterfall._distribute().protocol (src/FeeWaterfall.sol#217) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:331: EpochManager._checkBasket(EpochManager.Proposal).additions (src/EpochManager.sol#331) is a local variable never initialized\n[medium/medium] uninitialized-local at src/EpochManager.sol:368: EpochManager._checkQuorum(bytes32,bytes[]).last (src/EpochManager.sol#368) is a local variable never initialized\n[medium/medium] uninitialized-local at src/RebalanceExecutor.sol:311: RebalanceExecutor._nav().value (src/RebalanceExecutor.sol#311) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:216: FeeWaterfall._distribute().swarm (src/FeeWaterfall.sol#216) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IndexVault.sol:317: IndexVault._nav().tokenValue (src/IndexVault.sol#317) is a local variable never initialized\n[medium/medium] uninitialized-local at src/FeeWaterfall.sol:218: FeeWaterfall._distribute().utility (src/FeeWaterfall.sol#218) is a local variable never initialized\n[medium/medium] unused-return at src/RebalanceExecutor.sol:298: RebalanceExecutor._valueOf(address) (src/RebalanceExecutor.sol#298-302) ignores return value by (value,None) = registry.convert(token,balance,reserve) (src/RebalanceExecutor.sol#301)\n[medium/medium] unused-return at src/RebalanceExecutor.sol:208: RebalanceExecutor._authorize(address,address,uint256) (src/RebalanceExecutor.sol#208-266) ignores return value by (tradeValue,None) = registry.convert(sellToken,sellAmount,reserve) (src/RebalanceExecutor.sol#248)\n[low/medium] events-access at src/TimelockedAdmin.sol:179: TimelockedAdmin.setGuardian(address) (src/TimelockedAdmin.sol#179-184) should emit an event for: \n[low/medium] missing-zero-check at src/AssetRegistry.sol:154: AssetRegistry.setReserveFeed(address,uint32).feed (src/AssetRegistry.sol#154) lacks a zero-check on :\n[low/medium] missing-zero-check at src/FeeHookDeployer.sol:50: FeeHookDeployer.deploy(bytes32).deployed (src/FeeHookDeployer.sol#50) lacks a zero-check on :\n[low/medium] missing-zero-check at src/FeeWaterfall.sol:86: FeeWaterfall.constructor(address,address,address,address).weth_ (src/FeeWaterfall.sol#86) lacks a zero-check on :\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: (ok,ret) = held[i].staticcall(abi.encodeCall(IERC20.balanceOf,(address(vault)))) (src/RebalanceExecutor.sol#310)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: ! registry.isQuarantined(held[i]) (src/RebalanceExecutor.sol#320)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:310: IndexVault._nav() (src/IndexVault.sol#310-322) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#318)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:332: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#332-337) has external calls inside a loop: (ok,ret) = token.call{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.transfer,(to,amount))) (src/IndexVault.sol#333)\n[low/medium] calls-loop at src/IndexVault.sol:256: IndexVault.holdings() (src/IndexVault.sol#256-280) has external calls inside a loop: (values[i + 1],priced[i + 1]) = registry.convert(_held[i],balances[i + 1],asset) (src/IndexVault.sol#277)\n[low/medium] calls-loop at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-211) has external calls inside a loop: ! registry.isEligible(token) (src/EpochManager.sol#196)\n[low/medium] calls-loop at src/IndexVault.sol:310: IndexVault._nav() (src/IndexVault.sol#310-322) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#318)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) has external calls inside a loop: (ok,ret) = token.staticcall{gas: TRANSFER_GAS}(abi.encodeCall(IERC20.balanceOf,(address(this)))) (src/IndexVault.sol#327-328)\n[low/medium] calls-loop at src/EpochManager.sol:320: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#320-350) has external calls inside a loop: weight > registry.maxWeightBps(token) (src/EpochManager.sol#341)\n[low/medium] calls-loop at src/EpochManager.sol:362: EpochManager._checkQuorum(bytes32,bytes[]) (src/EpochManager.sol#362-376) has external calls inside a loop: admin.roleOf(signer) != Role.Signer (src/EpochManager.sol#372)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: (value,ok) = registry.convert(held[i],balance,reserve) (src/RebalanceExecutor.sol#315)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: (value,ok) = registry.convert(held[i],balance,reserve) (src/RebalanceExecutor.sol#315)\n[low/medium] calls-loop at src/IndexVault.sol:310: IndexVault._nav() (src/IndexVault.sol#310-322) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#318)\n[low/medium] calls-loop at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-211) has external calls inside a loop: q.weightsBps[i] > registry.maxWeightBps(token) (src/EpochManager.sol#197)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: ! registry.isQuarantined(held[i]) (src/RebalanceExecutor.sol#320)\n[low/medium] calls-loop at src/RebalanceExecutor.sol:306: RebalanceExecutor._nav() (src/RebalanceExecutor.sol#306-322) has external calls inside a loop: (ok,ret) = held[i].staticcall(abi.encodeCall(IERC20.balanceOf,(address(vault)))) (src/RebalanceExecutor.sol#310)\n[low/medium] calls-loop at src/EpochManager.sol:362: EpochManager._checkQuorum(bytes32,bytes[]) (src/EpochManager.sol#362-376) has external calls inside a loop: admin.roleOf(signer) != Role.Signer (src/EpochManager.sol#372)\n[low/medium] calls-loop at src/EpochManager.sol:320: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#320-350) has external calls inside a loop: ! registry.isEligible(token) (src/EpochManager.sol#338)\n[low/medium] calls-loop at src/IndexVault.sol:310: IndexVault._nav() (src/IndexVault.sol#310-322) has external calls inside a loop: (tokenValue,ok) = registry.convert(token,balance,asset) (src/IndexVault.sol#318)\n[low/medium] reentrancy-benign at src/FeeWaterfall.sol:210: Reentrancy in FeeWaterfall._distribute() (src/FeeWaterfall.sol#210-233):\n[low/medium] reentrancy-benign at src/FeeWaterfall.sol:135: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#135-145):\n[low/medium] reentrancy-benign at src/RebalanceExecutor.sol:102: Reentrancy in RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes) (src/RebalanceExecutor.sol#102-131):\n[low/medium] reentrancy-events at src/TimelockedAdmin.sol:121: Reentrancy in TimelockedAdmin.execute(address,uint256,bytes,bytes32) (src/TimelockedAdmin.sol#121-143):\n[low/medium] reentrancy-events at src/FeeWaterfall.sol:135: Reentrancy in FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16) (src/FeeWaterfall.sol#135-145):\n[low/medium] reentrancy-events at src/FeeWaterfall.sol:210: Reentrancy in FeeWaterfall._distribute() (src/FeeWaterfall.sol#210-233):\n[low/medium] return-bomb at src/IndexVault.sol:326: IndexVault._balanceOf(address) (src/IndexVault.sol#326-330) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] return-bomb at src/IndexVault.sol:332: IndexVault._tryTransfer(address,address,uint256) (src/IndexVault.sol#332-337) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/AssetRegistry.sol:302: AssetRegistry._feedPrice(address,uint8,uint32) (src/AssetRegistry.sol#302-319) uses timestamp for comparisons\n[low/medium] timestamp at src/AssetRegistry.sol:109: AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32) (src/AssetRegistry.sol#109-138) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:311: EpochManager._checkTiming(EpochManager.Proposal,uint256) (src/EpochManager.sol#311-318) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:221: EpochManager.clearExpired() (src/EpochManager.sol#221-225) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:214: EpochManager.cancelPending() (src/EpochManager.sol#214-218) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:298: EpochManager.basketStale() (src/EpochManager.sol#298-302) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:178: EpochManager.activate() (src/EpochManager.sol#178-211) uses timestamp for comparisons\n[low/medium] timestamp at src/RebalanceExecutor.sol:268: RebalanceExecutor._requireWindow() (src/RebalanceExecutor.sol#268-280) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:137: EpochManager.publish(EpochManager.Proposal,bytes[]) (src/EpochManager.sol#137-172) uses timestamp for comparisons\n[low/medium] timestamp at src/TimelockedAdmin.sol:146: TimelockedAdmin.cancel(bytes32) (src/TimelockedAdmin.sol#146-151) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:352: EpochManager._isActiveMember(address) (src/EpochManager.sol#352-358) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:320: EpochManager._checkBasket(EpochManager.Proposal) (src/EpochManager.sol#320-350) uses timestamp for comparisons\n[low/medium] timestamp at src/TimelockedAdmin.sol:121: TimelockedAdmin.execute(address,uint256,bytes,bytes32) (src/TimelockedAdmin.sol#121-143) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:231: EpochManager.anchorReport(uint64,bytes32,bytes[]) (src/EpochManager.sol#231-241) uses timestamp for comparisons\n[low/medium] timestamp at src/AssetRegistry.sol:253: AssetRegistry.isEligible(address) (src/AssetRegistry.sol#253-259) uses timestamp for comparisons\n[low/medium] timestamp at src/EpochManager.sol:288: EpochManager.targetWeightBps(address) (src/EpochManager.sol#288-294) uses timestamp for comparisons","passed":true},{"durationMs":1452,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/FeeHookDeployer.sol:64: `abi.encodePacked()` Hash Collision\n[high] reentrancy-state-change at src/AssetRegistry.sol:123: Reentrancy: State change after external call (21 places)\n[high] strict-equality-contract-balance at src/FeeWaterfall.sol:211: Dangerous strict equality checks on contract balances\n[high] unprotected-initializer at src/FeeHookDeployer.sol:62: Unprotected initializer\n[high] unsafe-casting at src/EpochManager.sol:159: Unsafe Casting of integers (2 places)\n[low] costly-loop at src/IndexVault.sol:193: Costly operations inside loop\n[low] large-numeric-literal at src/AssetRegistry.sol:27: Large Numeric Literal (13 places)\n[low] literal-instead-of-constant at src/AssetRegistry.sol:81: Literal Instead of Constant (27 places)\n[low] missing-inheritance at src/AssetRegistry.sol:14: Missing Inheritance (3 places)\n[low] non-reentrant-not-first at src/FeeWaterfall.sol:162: `nonReentrant` is Not the First Modifier\n[low] require-revert-in-loop at src/EpochManager.sol:194: Loop Contains `require`/`revert` (6 places)\n[low] state-no-address-check at src/AssetRegistry.sol:156: Address State Variable Set Without Checks\n[low] unchecked-return at src/FeeWaterfall.sol:111: Unchecked Return (3 places)\n[low] uninitialized-local-variable at src/EpochManager.sol:194: Uninitialized Local Variable (10 places)\n[low] unsafe-erc20-operation at src/IndexVault.sol:333: Unsafe ERC20 Operation\n[low] unused-public-function at src/FeeHookDeployer.sol:58: Public Function Not Used Internally\n[low] unused-state-variable at src/IndexVault.sol:28: Unused State Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b9b7d181fa84d3465d1b63b472d342781c50da242fd41704fa56f14368f94e87","verifiedTreeHash":"38745421de6f0d17282976125b9c34cb7ea2e40e","verifierVersion":"0.1.0+e6140b7a"},{"checks":[{"durationMs":8645,"exitCode":0,"name":"build","output":"Compiling 113 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.48s\nCompiler run successful!\n","passed":true},{"durationMs":2359,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 422923, ~: 428186)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 731712)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 874651)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1318135)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 417433)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 784499)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1515982)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94400)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 33.33ms (22.09ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384366, ~: 383289)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 371511)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352128)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114711)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 20120309)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 238127)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 689352)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 362642)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157561)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1131157)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810808)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 354088)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 425628)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 37.02ms (55.66ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1239828)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1415071)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1840734)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 977294)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798328)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67786)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338961)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964187)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199928)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1254774)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289617)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 288158)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216724)\n[PASS] test_rejectsBelowQuorum() (gas: 275939)\n[PASS] test_rejectsExpiredProposal() (gas: 149842)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694540)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150458)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513564)\n[PASS] test_rejectsMismatchedArrays() (gas: 469390)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163373)\n[PASS] test_rejectsQuarantinedToken() (gas: 243300)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807432)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289634)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859276)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 287014)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1691479)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1134972)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192219)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368930)\n[PASS] test_rejectsWrongEpoch() (gas: 191179)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157179)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508291)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496134)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 873664)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1327644)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 290156)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 656858)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 980676)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2291230)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2279650)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 43.53ms (35.00ms CPU time)\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85640, ~: 85564)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 45.58ms (46.17ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 70669, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150695)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 755359)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 112835)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 112721)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 856978)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 363941)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167465)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178522)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23672)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41472)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 507113)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 105042)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 554220)\n[PASS] test_paramsStayInsideHardBounds() (gas: 465592)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53223)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177708)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 399044)\n[PASS] test_routerAllowlist() (gas: 393268)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 104844)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1619006)\n[PASS] test_windowCannotExceedInterval() (gas: 107512)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 45.63ms (36.70ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 31335916)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 31381499)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 31453042)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 59043629)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 45.60ms (45.47ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 319500, ~: 324736)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 322563)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301575)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 113969)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 68310380)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 211662)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 648459)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 311553)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 968407)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1074474)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809854)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 328092)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 286431)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 400973)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 69.67ms (90.02ms CPU time)\n\nRan 17 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 384667, ~: 386763)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 377799)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357107)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114041)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 53713275)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 244667)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 694366)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 368843)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1157062)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1130339)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809938)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 348708)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 430596)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 69.97ms (94.28ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 438334, ~: 437292)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 3858103)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134169)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3055117)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 2907520)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2874975)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 524862)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 712862)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 472042)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 385649)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85381)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2843910)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1143353)\n[PASS] test_onlyKeepersTrade() (gas: 285286)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3221205)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 328771)\n[PASS] test_pauseStopsTrading() (gas: 130758)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3585737)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3558374)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5306886)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3085273)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 988313)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3607411)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 374560)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4111623)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4138694)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3806620)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4646668)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 289.01ms (116.28ms CPU time)\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 101781)\n[PASS] test_adminHandoverIsTwoStep() (gas: 479772)\n[PASS] test_adminWalletCannotHoldARole() (gas: 100455)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168733)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 135721)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 102197)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 96614)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 286414)\n[PASS] test_executorMustBeAContract() (gas: 111456)\n[PASS] test_guardianCanOnlyTighten() (gas: 257021)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 152176)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 394474)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 104869)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 121213)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 449040)\n[PASS] test_quorumBounds() (gas: 99422)\n[PASS] test_quorumCannotExceedSigners() (gas: 101554)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68212)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 156281)\n[PASS] test_signerCannotBecomeGuardian() (gas: 116638)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 479369)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 124478)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 297.52ms (5.73ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5594435, ~: 5603652)\n[PASS] test_depositCapBoundsTheBeta() (gas: 906482)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 841151)\n[PASS] test_depositHonoursMinShares() (gas: 380533)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4788527)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185781)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 31450739)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 653450)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 431084)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 303147)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4471639)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4687815)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 489895)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 504688)\n[PASS] test_redeemPaysProRataInKind() (gas: 4705882)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 351729)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4669386)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 4936359)\n[PASS] test_shareTokenMetadata() (gas: 36939)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 4856603)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4570054)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154817)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 349.63ms (396.80ms CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 32, calls: 2048, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 292   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 296   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 310   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 279   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 299   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 280   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 292   | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.21s (2.20s CPU time)\n\nRan 12 test suites in 2.21s (3.54s CPU time): 215 tests passed, 0 failed, 0 skipped (215 total tests)\n","passed":true},{"durationMs":96,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":262,\"docs/METHODOLOGY.md\":132,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":76,\"foundry.toml\":34,\"launch.json\":71,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":320,\"src/EpochManager.sol\":377,\"src/FeeHook.sol\":246,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":239,\"src/IndexVault.sol\":338,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":323,\"src/TimelockedAdmin.sol\":262,\"src/interfaces/IIndex.sol\":87,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":558,\"test/FeeHook.t.sol\":396,\"test/FeeWaterfall.t.sol\":235,\"test/IndexVault.t.sol\":365,\"test/LaunchToken.t.sol\":69,\"test/RebalanceExecutor.t.sol\":487,\"test/TimelockedAdmin.t.sol\":272,\"test/invariant/Handler.sol\":163,\"test/invariant/Solvency.invariant.t.sol\":95,\"test/utils/Fixture.sol\":279,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ee4bd8bbc805b0fd10bd620c15f7a690a35452944ef02082021a66ccf9fddfd5","verifiedTreeHash":"4c830226d1c1bc3e163f216f01ea5106446e3a86","verifierVersion":"0.1.0+e6140b7a"},{"checks":[{"durationMs":11187,"exitCode":0,"name":"build","output":"Compiling 118 files with Solc 0.8.26\nSolc 0.8.26 finished in 11.01s\nCompiler run successful!\n","passed":true},{"durationMs":7054,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 85563, ~: 85534)\n[PASS] test_metadataAndFixedSupplyMintedToDeployer() (gas: 51556)\n[PASS] test_noMintOrAdminEntryPointExists() (gas: 144953)\n[PASS] test_transferBeyondBalanceReverts() (gas: 32873)\n[PASS] test_transferFromNeedsAllowance() (gas: 137195)\n[PASS] test_transferMovesExactlyTheAmount() (gas: 74767)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.96ms (4.43ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteHighTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 385875, ~: 387399)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 374122)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 352194)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114689)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651832)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25638654)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 240738)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 698941)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 365253)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162729)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1186286)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 810786)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63429)\n[PASS] test_quoteIsCurrencyOne() (gas: 5631)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 746181)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 299243)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 357542)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 356623)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 431410)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 46.98ms (77.61ms CPU time)\n\nRan 14 tests for test/FeeWaterfall.t.sol:FeeWaterfallTest\n[PASS] testFuzz_distributionConservesEveryUnit(uint256,uint256,uint16,uint16) (runs: 256, μ: 422430, ~: 428246)\n[PASS] test_basketReserveIsDepositedAtNavForTheTreasury() (gas: 832760)\n[PASS] test_basketReserveKeepsAccumulatingWhileTheVaultIsClosed() (gas: 928119)\n[PASS] test_claimPaysOnlyTheConfiguredRecipient() (gas: 1329292)\n[PASS] test_configurationIsTimelockOnly() (gas: 102553)\n[PASS] test_distributeSplitsTheNonLpSeventyFivePercent() (gas: 388202)\n[PASS] test_pushHonoursMinShares() (gas: 422507)\n[PASS] test_rescueCannotTouchFeeAssets() (gas: 930998)\n[PASS] test_roundingDustGoesToTheBasketReserve() (gas: 246404)\n[PASS] test_splitChangeOnlyAffectsLaterFees() (gas: 789148)\n[PASS] test_splitMustSumToOneHundredPercent() (gas: 81248)\n[PASS] test_staleBasketSurchargeIsAppliedOnlyWhileStale() (gas: 1518932)\n[PASS] test_startingSplitAndFeeAreVisibleOnChain() (gas: 54016)\n[PASS] test_swapFeeIsBoundedAtThreePercentIncludingSurcharge() (gas: 94422)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 51.16ms (51.46ms CPU time)\n\nRan 24 tests for test/AssetRegistry.t.sol:AssetRegistryTest\n[PASS] testFuzz_convertRoundTripNeverCreatesValue(uint256,uint8,uint8) (runs: 256, μ: 69877, ~: 83242)\n[PASS] test_anyoneCanQuarantineAStaleTokenButNotAFreshOne() (gas: 150827)\n[PASS] test_approveTokenRejectsCodelessFeed() (gas: 759986)\n[PASS] test_approveTokenRejectsCodelessTokenAndFeed() (gas: 117462)\n[PASS] test_approveTokenRejectsReserveAsset() (gas: 117348)\n[PASS] test_approveTokenRejectsZeroCapAndFutureListing() (gas: 866232)\n[PASS] test_badFeedAnswersReadAsNoPrice() (gas: 364271)\n[PASS] test_convertFailsWhenReserveFeedIsStale() (gas: 70866)\n[PASS] test_convertHandlesDifferentDecimals() (gas: 172490)\n[PASS] test_defaultParametersMatchTheDocumentedStartingValues() (gas: 167510)\n[PASS] test_everySetterIsTimelockOnly() (gas: 178546)\n[PASS] test_freshPriceIsNormalisedTo18Decimals() (gas: 23738)\n[PASS] test_listedTokensAndAssetRecordArePublished() (gas: 41428)\n[PASS] test_methodologyVersionOnlyMovesForward() (gas: 514624)\n[PASS] test_minimumTokenAgeCannotGoBelowThirtyDays() (gas: 109669)\n[PASS] test_onlyGuardianExecutorOrTimelockQuarantine() (gas: 583244)\n[PASS] test_paramsStayInsideHardBounds() (gas: 473110)\n[PASS] test_priceIsValidAtHeartbeatAndStaleOneSecondLater() (gas: 53355)\n[PASS] test_revertingOrMalformedFeedDoesNotRevertTheReader() (gas: 177818)\n[PASS] test_revokedTokenIsNotEligibleButStillPriced() (gas: 402781)\n[PASS] test_routerAllowlist() (gas: 396934)\n[PASS] test_slippageLimitCannotBeSwitchedOff() (gas: 109471)\n[PASS] test_tokenYoungerThanThirtyDaysIsNotEligible() (gas: 1623567)\n[PASS] test_windowCannotExceedInterval() (gas: 112139)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 62.62ms (16.93ms CPU time)\n\nRan 26 tests for test/Revision.t.sol:RevisionTest\n[PASS] test_activationAtSnapshotAgeBoundarySucceeds() (gas: 1196785)\n[PASS] test_activationRechecksSnapshotAgeWithFreshPrices() (gas: 865609)\n[PASS] test_adminCanCancelRecoveryAndGuardianStillVetoesOtherCalls() (gas: 329121)\n[PASS] test_autoQuarantineDoesNotVetoProposalOrCloseFairDeposits() (gas: 3891522)\n[PASS] test_autoQuarantineRetainsDeltaAndWindowUntilGuardianConfirms() (gas: 6751411)\n[PASS] test_deadUntransferablePositionPreservesClaimsAndClosesDeposits() (gas: 5557737)\n[PASS] test_dustDepositCannotLockExistingShares() (gas: 5352326)\n[PASS] test_emptyBasketCannotResetTurnoverHistory() (gas: 1836695)\n[PASS] test_firstDepositChecksRegistryReserveScale() (gas: 157196)\n[PASS] test_firstTradeCannotChooseWindowStart() (gas: 1827439)\n[PASS] test_fullDustExitCanFreeHeldSlot() (gas: 5073260)\n[PASS] test_gasBurningUnquarantinedBalanceFailsWithBoundedError() (gas: 5596809)\n[PASS] test_guardianCannotVetoReplacementOrUnpause() (gas: 881301)\n[PASS] test_heldQuarantineClosesDepositsEvenWithFreshFeed() (gas: 4626889)\n[PASS] test_keeperFailuresCannotCreateExitRightsForAnyMember() (gas: 13567352)\n[PASS] test_lateFirstTradeUsesTheActivationSchedule() (gas: 2526980)\n[PASS] test_newSharesCannotRedeemInTheirDepositBlock() (gas: 4967089)\n[PASS] test_newSharesCannotTransferOrTransferFromToBypassLock() (gas: 4757506)\n[PASS] test_onlyKeeperOrTimelockChoosesTreasuryDepositTime() (gas: 504557)\n[PASS] test_proposalDelayAndSnapshotAgeMustLeaveExecutionTime() (gas: 1322352)\n[PASS] test_proposalDelayCannotConsumeEntireValidityPeriod() (gas: 33021)\n[PASS] test_quarantinedGasBurnerCannotStopUnrelatedTrades() (gas: 5283723)\n[PASS] test_releaseStartsANewFailureStreak() (gas: 6550330)\n[PASS] test_strictUnreadableBalancePreservesSharesAfterHoldingPeriod() (gas: 4506318)\n[PASS] test_unreadableNonStrictRedemptionEmitsUnknownSkippedSlice() (gas: 4549019)\n[PASS] test_untrackedDonationDoesNotBlockMemberPurchase() (gas: 1968485)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 65.81ms (85.36ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorsLeaveTheLaunchSupplyWithItsHolder() (gas: 32922360)\n[PASS] test_constructorsNeedNoExternalContractAndWireInDependencyOrder() (gas: 32968008)\n[PASS] test_constructorsRejectZeroAddresses() (gas: 4047)\n[PASS] test_launchGrantsNoRoleToAnyoneAndNothingToTheDeployer() (gas: 33039419)\n[PASS] test_runtimeFitsEip170AndContainsNoDelegatecallCallcodeOrSelfdestruct() (gas: 62564150)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 84.23ms (73.71ms CPU time)\n\nRan 23 tests for test/TimelockedAdmin.t.sol:TimelockedAdminTest\n[PASS] test_adminCannotBeHandedToARoleHolder() (gas: 106978)\n[PASS] test_adminHandoverIsTwoStep() (gas: 483912)\n[PASS] test_adminWalletCannotHoldARole() (gas: 105652)\n[PASS] test_configurationCannotBeCalledDirectly() (gas: 168689)\n[PASS] test_constructorValidatesInput() (gas: 3897)\n[PASS] test_executeAtExactlyReadyTimeSucceeds() (gas: 140963)\n[PASS] test_executeBubblesTargetRevertAndKeepsOperation() (gas: 107461)\n[PASS] test_executeRejectsCallDataForCodelessTarget() (gas: 98982)\n[PASS] test_executeRespectsDelayAndGraceBoundaries() (gas: 291612)\n[PASS] test_executorMustBeAContract() (gas: 116653)\n[PASS] test_guardianCanOnlyTighten() (gas: 256933)\n[PASS] test_guardianVetoesScheduledOperation() (gas: 157844)\n[PASS] test_minDelayChangeAppliesToLaterSchedules() (gas: 398614)\n[PASS] test_oneAddressHoldsAtMostOneRole() (gas: 110044)\n[PASS] test_onlyAdminSchedulesAndExecutes() (gas: 124107)\n[PASS] test_onlyGuardianPausesAndStrangerCannotUnpause() (gas: 472150)\n[PASS] test_quorumBounds() (gas: 104597)\n[PASS] test_quorumCannotExceedSigners() (gas: 106729)\n[PASS] test_rolesAreSeparatedAfterConfiguration() (gas: 68190)\n[PASS] test_scheduleRejectsShortLongAndDuplicate() (gas: 159131)\n[PASS] test_signerCannotBecomeGuardian() (gas: 144280)\n[PASS] test_timelockCannotDropSignersBelowQuorum() (gas: 487684)\n[PASS] test_treasuryEthLeavesOnlyThroughADelayedOperation() (gas: 129127)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 86.03ms (3.95ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookErc20QuoteLowTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 385496, ~: 383194)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 380397)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 357085)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114108)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 25733539)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 247265)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 703892)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 371441)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 1162204)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1185543)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809916)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63419)\n[PASS] test_quoteIsCurrencyZero() (gas: 5581)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 752656)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 292962)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 350941)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 351230)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 436368)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 87.73ms (95.13ms CPU time)\n\nRan 29 tests for test/RebalanceExecutor.t.sol:RebalanceExecutorTest\n[PASS] testFuzz_sandwichedFillNeverCostsMoreThanTheSlippageLimit(uint256,uint256) (runs: 256, μ: 454286, ~: 452980)\n[PASS] test_buysTheTopFiveAtTwentyPercentAndKeepsTheBuffer() (gas: 4023605)\n[PASS] test_cannotBuyATokenOutsideTheActiveBasket() (gas: 134150)\n[PASS] test_cannotBuyBeyondTheTargetDelta() (gas: 3184386)\n[PASS] test_cannotSellATokenAtOrBelowTarget() (gas: 3035744)\n[PASS] test_disposalIsOnlyForQuarantinedTokensAndEnforcesItsFloor() (gas: 2996731)\n[PASS] test_dustTradeWithNoEnforceableFloorIsRefused() (gas: 533159)\n[PASS] test_fillAtTheSlippageLimitSucceedsAndResetsTheFailureCount() (gas: 738772)\n[PASS] test_fillBelowTheOracleFloorIsUndoneAndCounted() (gas: 487730)\n[PASS] test_keeperFloorAppliesWhenStricterThanTheOracleFloor() (gas: 401293)\n[PASS] test_noTradingBeforeAnyBasketIsActive() (gas: 85362)\n[PASS] test_oneLegMustBeTheReserve() (gas: 2965570)\n[PASS] test_onlyAllowlistedRoutersAndNeverProjectContracts() (gas: 1152550)\n[PASS] test_onlyKeepersTrade() (gas: 285301)\n[PASS] test_outputIsMeasuredOnTheVaultSoFeeOnTransferTokensFailTheFloor() (gas: 3243973)\n[PASS] test_partialFillReturnsTheUnsoldRemainder() (gas: 344462)\n[PASS] test_pauseStopsTrading() (gas: 130761)\n[PASS] test_quarantinedPositionCanBeExitedAtAnyTime() (gas: 3704159)\n[PASS] test_quarantinedTokenWithDeadFeedIsDisposedThroughTheTimelock() (gas: 3680033)\n[PASS] test_removedMemberIsSoldAndItsReplacementBought() (gas: 5470643)\n[PASS] test_repeatedFailuresQuarantineTheAssetInsteadOfBlockingTheBasket() (gas: 3237194)\n[PASS] test_replacedExecutorLosesAccessToTheVault() (gas: 1024845)\n[PASS] test_reserveBufferCannotBeSpent() (gas: 3768650)\n[PASS] test_routerCannotReenterVaultOrExecutorDuringASwap() (gas: 390207)\n[PASS] test_sellsOnlyTheExcessOverTarget() (gas: 4268519)\n[PASS] test_smallDriftDoesNotTradeButMaterialDriftDoes() (gas: 4295590)\n[PASS] test_staleOracleStopsATradeUntilTheTokenIsQuarantined() (gas: 3905897)\n[PASS] test_swapEntryPointIsNotCallableFromOutside() (gas: 38611)\n[PASS] test_tradesOutsideTheWindowWaitForTheNextInterval() (gas: 4799416)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 97.15ms (152.95ms CPU time)\n\nRan 19 tests for test/FeeHook.t.sol:FeeHookNativeEthTest\n[PASS] testFuzz_feeAccountingMatchesAcrossSwapShapes(uint256,bool,bool) (runs: 256, μ: 319841, ~: 314126)\n[PASS] test_buyExactInput_feeTakenFromTheQuotePaid() (gas: 325161)\n[PASS] test_buyExactOutput_feeAddedToTheQuotePaid() (gas: 301553)\n[PASS] test_callbacksAreOnlyForThePoolManager() (gas: 114036)\n[PASS] test_deployerRejectsAQuoteTheWaterfallCannotAccountFor() (gas: 651810)\n[PASS] test_deployerRejectsWrongSaltAndSecondDeployment() (gas: 166127646)\n[PASS] test_dustSwapWithZeroFeeStillWorks() (gas: 214260)\n[PASS] test_feeAndSplitChangesReachTheHookThroughTheTimelockOnly() (gas: 657984)\n[PASS] test_feeEventIsIndexedByPoolSenderAndCurrency() (gas: 314151)\n[PASS] test_feeIsAlwaysCollectedInTheQuoteCurrencyNeverInTheProjectToken() (gas: 973549)\n[PASS] test_feesFlowFromSwapToWaterfallToVaultWithoutTradingTheBasket() (gas: 1129678)\n[PASS] test_hookAcceptsOnlyItsOnePool() (gas: 809832)\n[PASS] test_hookAddressCarriesExactlyTheRequiredPermissionBits() (gas: 63397)\n[PASS] test_nativeFeesArriveAsEthAndTheReceiveHookDoesNothing() (gas: 330690)\n[PASS] test_quoteSpecifiedExactInputExhaustedRangeRevertsAtomically() (gas: 638324)\n[PASS] test_quoteSpecifiedExactOutputPartialFillRevertsAtomically() (gas: 242556)\n[PASS] test_sellExactInput_feeTakenFromTheQuoteReceived() (gas: 288369)\n[PASS] test_sellExactOutput_traderStillReceivesTheExactQuote() (gas: 288953)\n[PASS] test_zeroHookFeeLeavesSwapsUntouched() (gas: 406745)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 148.23ms (163.07ms CPU time)\n\nRan 9 tests for test/adversarial/ProposalBoundaries.t.sol:ProposalBoundariesTest\n[PASS] testFuzz_everySignedFieldIsBoundToTheSignature(uint8,uint256) (runs: 1000, μ: 293141, ~: 351698)\n[PASS] test_clearExpiredAfterBoundaryPreservesReplayProtection() (gas: 973890)\n[PASS] test_clearExpiredAtBoundaryCannotEraseAnExecutableProposal() (gas: 1223886)\n[PASS] test_delayedAgeIncreaseIsRecheckedAtActivation() (gas: 1210651)\n[PASS] test_delayedMethodologyChangeInvalidatesPendingProposal() (gas: 1220104)\n[PASS] test_delayedRevocationIsRecheckedAtActivation() (gas: 1206769)\n[PASS] test_delayedWeightCapReductionIsRecheckedAtActivation() (gas: 1263405)\n[PASS] test_failedQuorumDoesNotPoisonARetryWithValidSignatures() (gas: 827291)\n[PASS] test_signatureCannotBeReusedOnAnotherManager() (gas: 325361)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 148.36ms (158.59ms CPU time)\n\nRan 9 tests for test/adversarial/ExternalTokenFailures.t.sol:ExternalTokenFailuresTest\n[PASS] testFuzz_taxedReserveCannotMintSharesForUnreceivedAssets(uint256) (runs: 1000, μ: 1016989, ~: 1018554)\n[PASS] test_failedFeeTransferRollsBackLiabilitiesAndUnlocksClaims() (gas: 1034185)\n[PASS] test_falseReturnStrictRollbackAndNonStrictExit() (gas: 1056885)\n[PASS] test_invalidBooleanStrictRollbackAndNonStrictExit() (gas: 1057230)\n[PASS] test_minShareFailureRollsBackFeeDistributionAndApproval() (gas: 898883)\n[PASS] test_noReturnTokenIsPaidDuringStrictRedemption() (gas: 941987)\n[PASS] test_reserveCallbacksCannotReenterOnDepositOrRedemption() (gas: 1272850)\n[PASS] test_shortReturnStrictRollbackAndNonStrictExit() (gas: 1056685)\n[PASS] test_taxedReserveOneUnitAndMaximumDeposit() (gas: 1980635)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 342.08ms (339.00ms CPU time)\n\nRan 22 tests for test/IndexVault.t.sol:IndexVaultTest\n[PASS] testFuzz_depositThenRedeemNeverReturnsMore(uint256,uint256) (runs: 256, μ: 5837777, ~: 5845334)\n[PASS] test_depositCapBoundsTheBeta() (gas: 1002963)\n[PASS] test_depositFeeAccruesToExistingHolders() (gas: 938312)\n[PASS] test_depositHonoursMinShares() (gas: 428641)\n[PASS] test_depositIsRefusedWhileAHeldTokenHasNoFreshPrice() (gas: 4976759)\n[PASS] test_depositRejectsZeroAmountAndZeroReceiver() (gas: 185649)\n[PASS] test_depositsStayClosedUntilAGuardianExists() (gas: 33037112)\n[PASS] test_donationInflationDoesNotStealFromTheNextDepositor() (gas: 749946)\n[PASS] test_executorCannotTakeUntrackedTokenOrEndWithoutBegin() (gas: 478022)\n[PASS] test_firstDepositMintsOneShareUnitPerReserveUnit() (gas: 350195)\n[PASS] test_laterDepositorPaysCurrentNav() (gas: 4694172)\n[PASS] test_navAndHoldingsFollowTheBasketAndPrices() (gas: 4870581)\n[PASS] test_onlyTheExecutorContractMovesAssets() (gas: 536811)\n[PASS] test_pauseClosesDepositsButNeverRedemptions() (gas: 553925)\n[PASS] test_redeemPaysProRataInKind() (gas: 4879947)\n[PASS] test_redeemRejectsZeroAndMoreThanBalance() (gas: 401017)\n[PASS] test_redeemWorksWithSwarmKeeperAndOraclesAllOffline() (gas: 4849001)\n[PASS] test_rescueCannotTouchReserveOrBasket() (gas: 5108067)\n[PASS] test_shareTokenMetadata() (gas: 36983)\n[PASS] test_tokenWithBrokenBalanceOfBlocksDepositsNotRedemptions() (gas: 5047402)\n[PASS] test_untransferableTokenCannotTrapHolders() (gas: 4746454)\n[PASS] test_wrongReserveDecimalsAreCaughtOnFirstDeposit() (gas: 154791)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 356.42ms (403.12ms CPU time)\n\nRan 40 tests for test/EpochManager.t.sol:EpochManagerTest\n[PASS] test_activatedProposalCannotBeReplayed() (gas: 1242734)\n[PASS] test_activationFailsOnceExpiredButSucceedsAtTheExpirySecond() (gas: 1418116)\n[PASS] test_basketGoesStaleWithoutSwarmActivityAndAReportRefreshesIt() (gas: 1843795)\n[PASS] test_basketMayHoldFewerThanFiveAndLeaveTheRestInReserve() (gas: 980485)\n[PASS] test_cancelledProposalCannotBeReplayed() (gas: 798269)\n[PASS] test_digestMatchesIndependentEip712Computation() (gas: 67665)\n[PASS] test_onlyOnePendingProposalUntilItExpires() (gas: 1338670)\n[PASS] test_pauseBlocksPublishAndActivate() (gas: 964025)\n[PASS] test_proposalSignedForAnOlderSignerSetIsRejected() (gas: 199917)\n[PASS] test_publishThenActivateAfterDelay() (gas: 1257669)\n[PASS] test_rejectsAProposalChangedAfterSigning() (gas: 289433)\n[PASS] test_rejectsASignatureFromANonSigner() (gas: 287974)\n[PASS] test_rejectsAttestedMarketDataBelowTheMinimums() (gas: 1216384)\n[PASS] test_rejectsBelowQuorum() (gas: 275755)\n[PASS] test_rejectsExpiredProposal() (gas: 149875)\n[PASS] test_rejectsExpiryThatCannotOutliveTheDelay() (gas: 694378)\n[PASS] test_rejectsExpiryTooFarAhead() (gas: 150491)\n[PASS] test_rejectsMalformedAndMalleableSignatures() (gas: 513185)\n[PASS] test_rejectsMismatchedArrays() (gas: 469498)\n[PASS] test_rejectsMoreThanFiveAssets() (gas: 163409)\n[PASS] test_rejectsQuarantinedToken() (gas: 243222)\n[PASS] test_rejectsStaleAndFutureSnapshots() (gas: 807292)\n[PASS] test_rejectsTheSameSignerTwice() (gas: 289450)\n[PASS] test_rejectsTokenThatIsNotAllowlisted() (gas: 859202)\n[PASS] test_rejectsTokenWithStalePrice() (gas: 286896)\n[PASS] test_rejectsTokenYoungerThanThirtyDays() (gas: 1696120)\n[PASS] test_rejectsTotalWeightAbove100Percent() (gas: 1146000)\n[PASS] test_rejectsWeightAboveTheTokenCap() (gas: 192211)\n[PASS] test_rejectsWhenNoQuorumIsConfigured() (gas: 368746)\n[PASS] test_rejectsWrongEpoch() (gas: 191234)\n[PASS] test_rejectsWrongMethodologyVersion() (gas: 157190)\n[PASS] test_rejectsZeroWeightAndDuplicates() (gas: 508033)\n[PASS] test_reportAnchorNeedsQuorumAndAFreshNewerSnapshot() (gas: 496202)\n[PASS] test_revokingASignerDuringTheDelayInvalidatesThePendingProposal() (gas: 879267)\n[PASS] test_secondEpochNeedsANewerSnapshot() (gas: 1330561)\n[PASS] test_signatureFromAnotherChainIsRejected() (gas: 289972)\n[PASS] test_staleFeedAtActivationBlocksActivation() (gas: 689091)\n[PASS] test_tokenQuarantinedDuringTheDelayBlocksActivation() (gas: 983533)\n[PASS] test_turnoverLimitCapsAdditionsPerEpoch() (gas: 2298890)\n[PASS] test_weeklyCadenceBlocksAnEarlySecondEpoch() (gas: 2285681)\nSuite result: ok. 40 passed; 0 failed; 0 skipped; finished in 356.45ms (38.24ms CPU time)\n\nRan 1 test for test/invariant/Solvency.invariant.t.sol:SolvencyInvariantTest\n[PASS]\nSolvencyInvariantTest invariants:\n[PASS] invariant_executorHoldsNothingBetweenTrades\n[PASS] invariant_feeAccountingConserves\n[PASS] invariant_noPropertyViolatedMidSequence\n[PASS] invariant_vaultIsSolventAndConsistent\n SolvencyInvariantTest invariants (runs: 32, calls: 2048, reverts: 0)\n\n╭----------+-------------------+-------+---------+----------╮\n| Contract | Selector          | Calls | Reverts | Discards |\n+===========================================================+\n| Handler  | claim             | 286   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | collectFees       | 307   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | deposit           | 311   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | movePrice         | 269   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | pushBasketReserve | 299   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | rebalance         | 275   | 0       | 0        |\n|----------+-------------------+-------+---------+----------|\n| Handler  | redeem            | 301   | 0       | 0        |\n╰----------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.20s (2.19s CPU time)\n\nRan 1 test for test/adversarial/TreasurySequence.invariant.t.sol:TreasurySequenceInvariantTest\n[PASS] invariant_treasuryAndFixedSupplyConserveValue() (runs: 256, calls: 24576, reverts: 0)\n\n╭-------------------------+---------------------+-------+---------+----------╮\n| Contract                | Selector            | Calls | Reverts | Discards |\n+============================================================================+\n| TreasurySequenceHandler | advance             | 3503  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | cancel              | 3415  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | execute             | 3536  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | fund                | 3608  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | moveLaunchTokens    | 3600  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | schedule            | 3424  | 0       | 0        |\n|-------------------------+---------------------+-------+---------+----------|\n| TreasurySequenceHandler | unauthorizedExecute | 3490  | 0       | 0        |\n╰-------------------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.81s (3.81s CPU time)\n\nRan 1 test for test/adversarial/CustodySequence.invariant.t.sol:CustodySequenceInvariantTest\n[PASS]\nCustodySequenceInvariantTest invariants:\n[PASS] invariant_allSharesAreBackedAndAccountedFor\n[PASS] invariant_everyReserveUnitHasADestination\n CustodySequenceInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭------------------------+----------------+-------+---------+----------╮\n| Contract               | Selector       | Calls | Reverts | Discards |\n+======================================================================+\n| CustodySequenceHandler | changeSplit    | 1994  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | claim          | 2033  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | deposit        | 2096  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | distribute     | 2020  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | donate         | 2076  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | push           | 1989  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | receiveFees    | 2155  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | redeem         | 2016  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | roundTrip      | 2038  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | togglePause    | 2093  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | transferShares | 2013  | 0       | 0        |\n|------------------------+----------------+-------+---------+----------|\n| CustodySequenceHandler | treasuryRedeem | 2053  | 0       | 0        |\n╰------------------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.89s (6.88s CPU time)\n\nRan 17 test suites in 6.90s (14.85s CPU time): 267 tests passed, 0 failed, 0 skipped (267 total tests)\n","passed":true},{"durationMs":106,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AssetRegistry.approveToken(address,address,uint32,uint16,uint40,bytes32)\",\"AssetRegistry.quarantine(address)\",\"AssetRegistry.quarantineIfStale(address)\",\"AssetRegistry.releaseQuarantine(address)\",\"AssetRegistry.revokeToken(address)\",\"AssetRegistry.setMethodology(uint32,bytes32)\",\"AssetRegistry.setParam(uint8,uint256)\",\"AssetRegistry.setReserveFeed(address,uint32)\",\"AssetRegistry.setRouter(address,bool)\",\"EpochManager.activate()\",\"EpochManager.anchorReport(uint64,bytes32,bytes[])\",\"EpochManager.cancelPending()\",\"EpochManager.clearExpired()\",\"EpochManager.publish((uint64,uint64,uint64,uint32,uint32,bytes32,address[],uint16[],uint256[],uint256[],uint256[]),bytes[])\",\"FeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"FeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"FeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"FeeHookDeployer.deploy(bytes32)\",\"FeeWaterfall.claim(uint8)\",\"FeeWaterfall.distribute()\",\"FeeWaterfall.pushBasketReserve(uint256)\",\"FeeWaterfall.receive()\",\"FeeWaterfall.rescue(address,address)\",\"FeeWaterfall.setRecipient(uint8,address)\",\"FeeWaterfall.setSplit(uint16,uint16,uint16,uint16,uint16)\",\"FeeWaterfall.setSwapFee(uint24,uint24)\",\"IndexVault.approve(address,uint256)\",\"IndexVault.beginTrade(address,uint256)\",\"IndexVault.deposit(uint256,address,uint256)\",\"IndexVault.endTrade(address)\",\"IndexVault.redeem(uint256,address,bool)\",\"IndexVault.rescue(address,address)\",\"IndexVault.setDepositCap(uint256)\",\"IndexVault.transfer(address,uint256)\",\"IndexVault.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"RebalanceExecutor.disposeQuarantined(address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.executeTrade(address,address,uint256,uint256,address,bytes)\",\"RebalanceExecutor.swapThroughRouter(address,address,uint256,uint256,address,bytes)\",\"TimelockedAdmin.acceptAdmin()\",\"TimelockedAdmin.cancel(bytes32)\",\"TimelockedAdmin.execute(address,uint256,bytes,bytes32)\",\"TimelockedAdmin.pause()\",\"TimelockedAdmin.receive()\",\"TimelockedAdmin.revokeKeeper(address)\",\"TimelockedAdmin.revokeSigner(address)\",\"TimelockedAdmin.schedule(address,uint256,bytes,bytes32,uint256)\",\"TimelockedAdmin.setExecutor(address)\",\"TimelockedAdmin.setGuardian(address)\",\"TimelockedAdmin.setKeeper(address,bool)\",\"TimelockedAdmin.setMinDelay(uint256)\",\"TimelockedAdmin.setQuorum(uint32)\",\"TimelockedAdmin.setSigner(address,bool)\",\"TimelockedAdmin.transferAdmin(address)\",\"TimelockedAdmin.unpause()\"],\"files\":{\".gitignore\":3,\"README.md\":287,\"docs/METHODOLOGY.md\":139,\"docs/PROPOSAL.md\":99,\"docs/SECURITY.md\":95,\"foundry.toml\":34,\"script/Deploy.s.sol\":65,\"src/AssetRegistry.sol\":336,\"src/EpochManager.sol\":382,\"src/FeeHook.sol\":258,\"src/FeeHookDeployer.sol\":86,\"src/FeeWaterfall.sol\":242,\"src/IndexVault.sol\":368,\"src/LaunchToken.sol\":16,\"src/RebalanceExecutor.sol\":344,\"src/TimelockedAdmin.sol\":271,\"src/interfaces/IIndex.sol\":91,\"test/AssetRegistry.t.sol\":298,\"test/Deployment.t.sol\":139,\"test/EpochManager.t.sol\":560,\"test/FeeHook.t.sol\":433,\"test/FeeWaterfall.t.sol\":239,\"test/IndexVault.t.sol\":366,\"test/LaunchToken.t.sol\":69,\"test/RebalanceExecutor.t.sol\":488,\"test/Revision.t.sol\":426,\"test/TimelockedAdmin.t.sol\":272,\"test/adversarial/CustodySequence.invariant.t.sol\":315,\"test/adversarial/ExternalTokenFailures.t.sol\":212,\"test/adversarial/ProposalBoundaries.t.sol\":129,\"test/adversarial/README.md\":42,\"test/adversarial/TreasurySequence.invariant.t.sol\":224,\"test/invariant/Handler.sol\":165,\"test/invariant/Solvency.invariant.t.sol\":95,\"test/utils/Fixture.sol\":282,\"test/utils/Mocks.sol\":180,\"test/utils/V4Router.sol\":78},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f21a5ac7d491dd15bf0ffe4b7f7b5135ba2b22429013618f3f301b8302d3195b","verifiedTreeHash":"22ba9f64090f691bd9cb29bdf30778f259293ced","verifierVersion":"0.1.0+94826a22"}]}