{"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":"f932bf3e-9cdc-490f-b0b1-d9666ed74d48","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"84ba9d490691b8df5b5e728cf886010d324e5b910527eee45005483c807fb018","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"0ca498656287026b8f6c112361c729ce6d1a4151140e34f0ab5286f643cc8b75","dependsOn":["adapt_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":"e2e11271405b3f35929a8250b7a3f715577cbb8d0e3627a1cf7ca80eef767210","dependsOn":["adapt_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":"30741c6316f8c1594d0dd6323e2ad4f0bb310d3a411508f994487ba62d25420d","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"d68d38c7d22672c327edf29e5f7e5d397c66a41d54621bfadb72f143678af594","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"824631bd2926ea8249ef63e162d003aa3538758ec5bc72228b963f14b5072d4b","dependsOn":["adapt_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":"f81685712bd630cb3b796fb4221e9f64b046145b79be8886ae3687fcd81e9b64","dependsOn":["adapt_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":"f08da0d69e6c96f54b807af2c3945951848d2bd47f8165c23b9945295fc1bef4","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"0c54c1d3203b610c7a59f2125b537238cd12905efdd9fe528b863733ec799fc3","dependsOn":["adapt_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":"Complete the missing application deployment for the existing PRISM RIOT project on Ethereum mainnet. Launch kind: evm_contracts. Deploy only FeeTreasury, StakingVault, Arena and OracleAdapter; do not deploy a token, hook, distributor or pool.\n\nCompleted source job: https://explorer.imd.fun/jobs/9bb0ae93-2a65-44d2-900e-e06b9ad5e794 . Use the reviewed repository https://github.com/identity-md-launches/launch-1153-build-test-independently-review at commit 34e992ab84195173cd35219f895309b051b1944d. Preserve the reviewed economics, game rules, security fixes and 0.5% hook fee. Adapt the deployment manifest for these four application contracts; keep already live assets unchanged.\n\nExisting PRIO: 0xfd1c234972768c23bb21d655966e0b122dd67a2c. Existing TreasuryFeeHook: 0x65a783cc6725a02ce349dc4d72577994df1760cc. PoolManager: 0x000000000004444c5dc75cb358380d2e3de08a90. Existing project owner: 0x13afb9b5780cd9ae79c61503adb69c57845d8eac; assign all application owner roles to this wallet. Original deployment transaction: 0x545df1adb27c4a2ad6de57dd3d4d28005306f1471c0002381d5518fbc1c6dd7d. Verify these records and check for existing application deployments before creating duplicates. The hook treasury() was zero at the last check; do not invent companion addresses.\n\nFeeTreasury constructor: existing PoolManager and project owner. StakingVault and Arena constructors: project owner and existing PRIO. OracleAdapter constructor: project owner and the current official IMD Ethereum oracle signer, verified from current primary protocol sources. Use the contracts-only factory's supported static constructor arguments and $owner; no unsupported post-deploy factory callbacks. Audit the manifest and deployment adaptation independently, run the reviewed regression/invariant tests, then deploy only the four modules in one contracts-only launch.\n\nReturn live addresses, transaction receipts, ABIs and updated source. Prepare and simulate the owner-only configuration transactions described by README's After launch section: bind the hook and PRIO in FeeTreasury before binding the treasury in the existing hook; set staking funder, Arena oracle, one-time sinks, bounded executor, reserve/spending limits and price floors. Verify destinations carefully because some bindings are permanent. Provide the transaction targets and calldata in the required order for the owner wallet to review/sign; do not claim preparation equals execution.\n\nPreserve the fee-funded economy: capped operating reserve, then 30% IMD work, 30% PRIO rewards, 40% owner; PRIO rewards split equally between staking and games. No owner operating advances, extra minting or guaranteed returns. Keep player principal/escrow/refunds separate. Leave paid game/oracle operations disabled until configuration, funded reserves, prizes and server operator are ready. Document current IMD pool/Intake configuration and operator budgets; handle PriceLimitAlreadyExceeded by backing off until pool liquidity returns rather than repeatedly wasting gas. Deliver GitHub source, setup instructions and a deployment/configuration checklist. No website in this order.","parentJobId":null,"planHash":"2d7cbe88793976acace93949dd63817a5615a61e625237c092ccc17219f64410","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"f932bf3e-9cdc-490f-b0b1-d9666ed74d48","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1158-complete-missing-application-deployment"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51250","feedbackHash":"d087e387d95a4b9dbf944b0e8fd0d943cab7fb91fc05f4084ef017024156c1a4","nodeKey":"adapt_contract_project","submissionHash":"84ba9d490691b8df5b5e728cf886010d324e5b910527eee45005483c807fb018","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51203","feedbackHash":"2ae7e9a02f06f78e06d4fef6933bcbc02ac91ddd4422f47d091c372979104f84","nodeKey":"adapt_contract_project","submissionHash":"dc9b678f5479758f1fc1895ee2674654dc601f09b790c540696a208140d7fbd6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51021","feedbackHash":"2f328a9cc81130b5f83ded76fa09de7ec629a2155877e9fa74a825d139ee9a0f","nodeKey":"audit_economics","submissionHash":"0ca498656287026b8f6c112361c729ce6d1a4151140e34f0ab5286f643cc8b75","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51250","feedbackHash":"f441ea67851a33f489eac6b00cd471335e29124a31f87bf0cbe1e03e8ddc8b66","nodeKey":"audit_flow","submissionHash":"e2e11271405b3f35929a8250b7a3f715577cbb8d0e3627a1cf7ca80eef767210","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51885","feedbackHash":"4b9e008bd2d6afd96079fa3857de0edce5554b0565b40b605e85424c28867664","nodeKey":"audit_imported_code","submissionHash":"30741c6316f8c1594d0dd6323e2ad4f0bb310d3a411508f994487ba62d25420d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52230","feedbackHash":"4c88aa06222b93ad70eaf0dd37d122623fb080d73ca2115b789e76b9b3714457","nodeKey":"audit_judge","submissionHash":"d68d38c7d22672c327edf29e5f7e5d397c66a41d54621bfadb72f143678af594","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50966","feedbackHash":"a351bd74e97d964e356b2ecb85a088ea535b6580bae6599804c353ac1a561341","nodeKey":"audit_judge","submissionHash":"45d0440248caa0296a3da7a1be751a4f249ee6cec8e5ecb78cf189c636d8d9b2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52252","feedbackHash":"b0d586522efb52f1abde8c0d1552c9b78fe03fe15f2323343ad253b186889de3","nodeKey":"audit_math","submissionHash":"824631bd2926ea8249ef63e162d003aa3538758ec5bc72228b963f14b5072d4b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51452","feedbackHash":"811d82673418747cb76cc9122a742f0894b609b0abaa98da4e480d462412398a","nodeKey":"audit_permissions","submissionHash":"f81685712bd630cb3b796fb4221e9f64b046145b79be8886ae3687fcd81e9b64","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52082","feedbackHash":"9d7997536f676e0a5975432dc3693384f6943a338225a68c12283c1bd54b0ee0","nodeKey":"manifest","submissionHash":"5125d7bff4e22e263f7aada80c0570ee74833e2e79f9c1d3eb6267174c8fe9ba","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51882","feedbackHash":"47a8433c5f68941aa6b3a78edb0a0ccd6beea312ef6ee5d849a72c8ead6ad6c6","nodeKey":"manifest","submissionHash":"f08da0d69e6c96f54b807af2c3945951848d2bd47f8165c23b9945295fc1bef4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51879","feedbackHash":"d91046d00462988577cf4b4867c4e2733a1cf580bedb76215bd8feb2a24bac64","nodeKey":"write_foundry_tests","submissionHash":"0c54c1d3203b610c7a59f2125b537238cd12905efdd9fe528b863733ec799fc3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51885","feedbackHash":"febc15c23aa7702d78c82a3ac7633f99ec2758b70efc69f6a5f05d41f1cedd09","nodeKey":"write_foundry_tests","submissionHash":"57c17e145167eb49fdf4124cc1b05959eaa01636c8f25315ea9b59f51518f8f0","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"d28f21079fb2745039b1352d3a837e0193e1ce37e3f44cdafd19b8f454b598b7","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"22e4823f6eb4de8885e5d3c444276681c20b097a60e119cabb23b295e5b3242e","device":"4faf975f1178e1f8","findings":[],"hash":"0c54c1d3203b610c7a59f2125b537238cd12905efdd9fe528b863733ec799fc3","nodeId":"ec603212-3511-40c5-890b-29d3bcb1875f","outcome":"completed","summary":"The revision is complete and verified. The full suite passes.\n\n| Check | Result |\n| --- | --- |\n| `forge build` | compiles |\n| `forge test` | 198 passed, 0 failed, 25 suites |\n| Adapter invariant handler | 0 handler-level reverts (was 170 silent ones) |\n| `forge fmt --check` on my files | clean |\n\n**What the other contributor's revision broke in my accepted tests, and how I adapted.** The source revision made `OracleAdapter.setArena` one-shot and added a `RoundNotCreated` guard that refuses paid requests and results for rounds the bound Arena has not created. Two tests in `test/LaunchRehearsal.t.sol` failed on that:\n\n- The plan-replay test now expects A7 to be the only Phase A step that refuses a second signature before money moves, and after a PRIO purchase expects A1, A2, A4 and A7 to be frozen while A3 with the same three addresses is still accepted.\n- The paid-operations test now expects `RoundNotCreated` after the plan, then opens a zero-prize round to reach the original unfunded-balance refusal.\n\n**New failure-path coverage on the replica** in the same file:\n\n- The per-line freeze: after a PRIO purchase the vault, arena and PRIO token refuse to move, the OracleAdapter sink and IMD token still correct, and after the first IMD purchase that line freezes too while only the pool venue stays movable.\n- Signing A7 after the first paid request: the answer is refused, the open request blocks a re-pin, the price is spent, and recovery works after the stale timeout. This documents the cost of the ordering the checklist warns about.\n\n**Invariant harness fixes, which matter more than the unit tests.** In `test/LaunchRehearsalInvariants.t.sol` the oracle action had become silently vacuous: with the Arena bound by the plan, every request reverted and the tolerant runner hid it. The handler now opens a real Arena round with a prize from the game pool before buying its answer, and the prize-pool invariant accounts for locked prizes. In `test/OracleAdapterInvariants.t.sol` the handler gains an Arena stub, a one-shot `setArena` action, round creation and re-pins, with two new invariants: results stored under the Arena check belong only to created rounds, and a pin never changes once its round exists. I also found and fixed a long-standing mistake of my own there: the stranger-relay action's signature helper consumed the `expectRevert`, so that assertion had never reached the contract. I confirmed every new handler path executes with a scratch test, which is not submitted.\n\n**No findings file.** I read the revised code adversarially and found nothing I had to test around. The one edge I would flag is informational and already documented: a request made before A7 is signed strands its price, recoverable after the two-day timeout, and the test above pins that behaviour down.\n\nOnly my three test files changed. Nothing is committed; the working tree holds the revision.","treeHash":"778c64df9d2de0213064f554427e6d564a80bc6a","usage":{"cachedInputTokens":2967679,"inputTokens":866,"model":"claude-fable-5-1","outputTokens":51041,"runtime":"claude","turns":52,"wallClockMs":732225}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"72ae9b5bbd1a54b6","findings":[{"citation":"resolved","description":"The contract notice (src/FeeTreasury.sol:55-57) states that once a purchase has happened the bindings are immutable 'so the 30% PRIO and 30% IMD allocations can never be redirected once money has flowed'. setPrio (line 168) and setSinks (line 187) enforce this with `if (purchased) revert AlreadySet();`. setImd (line 175) and setImdPool (line 235) have no such guard. After the first purchase the owner can call setImd(X) for any ERC20 X, setImdPool(fee, spacing, hooks) for a pool the owner provides all the liquidity in (or whose hook the owner controls), and setPriceFloors(_, 1); the executor's next buyImd then converts the IMD budget's ETH into X inside the owner's pool, and the owner takes the ETH back by removing liquidity. The 'agent work' 30% of every fee is therefore redirectable to the owner at any time, in two owner transactions, which contradicts the guarantee the contract states and the brief's fixed 30% IMD work allocation. This is owner power, but the code's own promise is that this power ends at the first purchase, as it does for PRIO: the asymmetry is the defect. Invariant broken: 'after `purchased`, the destination of every budget line is fixed'. Fix preserving the design: `if (purchased) revert AlreadySet();` in setImd (as in setPrio). For setImdPool either freeze it the same way or, if pool migration must stay possible, only accept `hooks == address(0)` after `purchased` (an owner hook on the pool is the other redirect path). If an IMD token migration must remain possible after purchases, that is a scope decision: a new FeeTreasury, or a time-locked change, rather than an instant setter.","line":175,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev After the first purchase PRIO and the sinks are frozen (setPrio/setSinks revert AlreadySet) so the\n/// 30% PRIO and 30% IMD allocations \"can never be redirected once money has flowed\" (FeeTreasury notice).\n/// The IMD token is not frozen: setImd(anything) still succeeds, so the IMD line can be pointed at any token\n/// and any pool after money has flowed. Fails on the current code; passes once setImd is frozen like setPrio.\ncontract ProofA_ImdFreezeTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n    address fakeHook = makeAddr(\"hook\"); // FeeTreasury.receive only checks msg.sender == hook\n    address adapterAddr = makeAddr(\"adapter\");\n\n    PoolManager manager;\n    PoolModifyLiquidityTest lpRouter;\n    PrismRiotToken prio;\n    PrismRiotToken imd;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        lpRouter = new PoolModifyLiquidityTest(IPoolManager(address(manager)));\n        prio = new PrismRiotToken();\n        imd = new PrismRiotToken();\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(prio));\n        arena = new Arena(owner, address(prio));\n        vm.startPrank(owner);\n        treasury.bindHook(fakeHook);\n        treasury.setPrio(address(prio));\n        treasury.setSinks(address(vault), address(arena), adapterAddr);\n        treasury.setExecutor(executor);\n        treasury.setPriceFloors(1, 1);\n        vault.setRewardFunder(address(treasury));\n        vm.stopPrank();\n        // 10 ETH of fee income, allocated: 30% of it is the IMD budget.\n        vm.deal(fakeHook, 100 ether);\n        vm.prank(fakeHook);\n        (bool ok,) = address(treasury).call{value: 10 ether}(\"\");\n        require(ok);\n        treasury.allocate();\n        // An ETH/IMD pool with liquidity, no hook.\n        PoolKey memory key = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 3000,\n            tickSpacing: 60,\n            hooks: IHooks(address(0))\n        });\n        manager.initialize(key, SQRT_PRICE_1_1);\n        imd.approve(address(lpRouter), type(uint256).max);\n        vm.deal(address(this), 1_000 ether);\n        lpRouter.modifyLiquidity{value: 200 ether}(\n            key, ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 100 ether, bytes32(0)), \"\"\n        );\n    }\n\n    function test_imdTokenIsFrozenAfterFirstPurchaseLikePrio() public {\n        vm.startPrank(owner);\n        treasury.setImd(address(imd));\n        treasury.setImdPool(3000, 60, address(0));\n        vm.stopPrank();\n        vm.prank(executor);\n        uint256 out = treasury.buyImd(0.1 ether, 1);\n        assertGt(out, 0);\n        assertTrue(treasury.purchased(), \"money has flowed\");\n\n        // PRIO and the sinks are frozen, as documented.\n        vm.prank(owner);\n        vm.expectRevert(FeeTreasury.AlreadySet.selector);\n        treasury.setPrio(address(imd));\n        vm.prank(owner);\n        vm.expectRevert(FeeTreasury.AlreadySet.selector);\n        treasury.setSinks(address(vault), address(arena), owner);\n\n        // The IMD line must be frozen the same way. Today this call succeeds and the next buyImd, after a\n        // setImdPool for the owner's coin, spends the IMD budget on that coin.\n        PrismRiotToken ownerCoin = new PrismRiotToken();\n        vm.prank(owner);\n        vm.expectRevert(FeeTreasury.AlreadySet.selector);\n        treasury.setImd(address(ownerCoin));\n        assertEq(address(treasury.imd()), address(imd), \"the IMD destination did not move\");\n    }\n}","reproduction":"State: FeeTreasury with hook bound, setPrio(PRIO), setSinks(vault, arena, adapter), executor and floors set, 10 ETH of fee income allocated (reserve 0.5, imdBudget 2.85 ETH, prioBudget 2.85 ETH, ownerBudget 3.8 ETH), setImd(IMD) and setImdPool(3000, 60, 0) on an ETH/IMD pool with liquidity. Calls: executor buyImd(0.1 ether, 1) -> out > 0, purchased == true. Owner setPrio(IMD) -> reverts AlreadySet (as documented). Owner setSinks(vault, arena, owner) -> reverts AlreadySet. Owner setImd(ownerCoin) -> expected: reverts AlreadySet; actual: succeeds, imd() == ownerCoin, imdPoolSet == false. Then setImdPool(...) for the owner's ownerCoin pool succeeds and the next buyImd(0.25 ether, 0) with a floor of 1 spends 0.25 ETH of the IMD budget into that pool. Proof: test/scratch/ProofA_ImdFreeze.t.sol (fails now with 'next call did not revert as expected' on setImd).","severity":"medium","snippet":"    function setImd(address imd_) external onlyOwner {\n        if (imd_ == address(0)) revert ZeroAddress();\n        imd = IERC20(imd_);","title":"FeeTreasury: the 30% IMD line can be redirected after money has flowed; setImd is not frozen by `purchased` (setPrio/setSinks are)"},{"citation":"resolved","description":"withdrawToken documents that 'The configured payment asset (the IMD bought from fees for agent work) cannot be withdrawn: it is spent only on panel answers' and enforces it with `token == asset`. `asset` is re-settable through setPayment at any time (line 153), so the lock is two owner calls deep: setPayment(otherToken, 1) then withdrawToken(IMD, owner, balance). FeeTreasury freezes `oracleAdapter` as the IMD sink after the first purchase precisely so the 30% IMD line cannot be redirected; the adapter hands it back out. Together with finding 1 the entire fee-funded IMD allocation is owner-extractable, which the brief's economy (30% IMD work, no owner extras beyond the 40%) does not allow. Owner power, but the contract states the opposite guarantee, so it is a broken guarantee rather than a design preference. Fix: record every token that has ever been the payment asset (`mapping(address => bool) lockedAsset`, set in setPayment) and refuse it in withdrawToken; or make the asset one-shot.","line":347,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev OracleAdapter.withdrawToken promises that the IMD bought from fees for agent work \"cannot be\n/// withdrawn: it is spent only on panel answers\". The check is `token == asset` at call time, and `asset`\n/// is re-settable, so two owner calls (setPayment to another token, then withdrawToken) take the IMD out.\n/// Fails on the current code; passes once a token that has ever been the payment asset stays locked.\ncontract ProofB_AdapterWithdrawTest is Test {\n    address owner = makeAddr(\"owner\");\n    address constant SIGNER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;\n\n    function test_imdBoughtForAgentWorkCannotBeWithdrawnAfterAssetSwitch() public {\n        PrismRiotToken imd = new PrismRiotToken();\n        PrismRiotToken other = new PrismRiotToken();\n        OracleAdapter adapter = new OracleAdapter(owner, SIGNER);\n        vm.prank(owner);\n        adapter.setPayment(address(imd), 0.5 ether);\n        // IMD the treasury bought from fees for panel answers (FeeTreasury.buyImd sends it here).\n        imd.transfer(address(adapter), 10 ether);\n        vm.prank(owner);\n        vm.expectRevert(OracleAdapter.AssetNotWithdrawable.selector);\n        adapter.withdrawToken(address(imd), owner, 10 ether);\n        // Point `asset` elsewhere, then withdraw the same IMD.\n        vm.prank(owner);\n        adapter.setPayment(address(other), 1);\n        vm.prank(owner);\n        vm.expectRevert(OracleAdapter.AssetNotWithdrawable.selector); // expected; today 10 IMD go to the owner\n        adapter.withdrawToken(address(imd), owner, 10 ether);\n        assertEq(imd.balanceOf(address(adapter)), 10 ether, \"IMD bought for agent work stays for agent work\");\n    }\n}","reproduction":"State: OracleAdapter(owner, signer); owner setPayment(IMD, 0.5e18); the adapter holds 10 IMD (what FeeTreasury.buyImd delivers). Calls: owner withdrawToken(IMD, owner, 10e18) -> reverts AssetNotWithdrawable (correct). Owner setPayment(OTHER, 1) -> ok. Owner withdrawToken(IMD, owner, 10e18) -> expected: reverts AssetNotWithdrawable; actual: succeeds, adapter IMD balance 0, owner +10 IMD. Proof: test/scratch/ProofB_AdapterWithdraw.t.sol (fails now with 'next call did not revert as expected').","severity":"medium","snippet":"    function withdrawToken(address token, address to, uint256 amount) external onlyOwner {\n        if (token == asset) revert AssetNotWithdrawable();","title":"OracleAdapter: IMD bought for agent work is withdrawable by the owner after re-pointing `asset`; the 'cannot be withdrawn' guarantee only checks the current asset"},{"citation":"resolved","description":"_swapEthFor only refuses `out < minOut` and `out < spent * floor / 1e18`. Against an initialized ETH/IMD pool with no in-range liquidity (docs/DEPLOYMENT.md section 4 lists six such live ETH/IMD pools, and the proposed pool can be drained to that state) the PoolManager returns a zero delta: out = 0, spent = 0. With minImdOut = 0, which operator/operator.py cmd_buy always passes, both checks pass (0 < 0 is false), buyImd succeeds, `purchased = true`, and `imd.safeTransfer(oracleAdapter, 0)` is sent. Consequences: (a) setPrio and setSinks become immutable although the contract notice promises they are correctable 'until the first purchase'; a sink typo can no longer be fixed even though nothing was ever bought; (b) the operator's simulate-first guard does not catch it (the simulation succeeds), so a real transaction is broadcast that buys nothing and the backoff is never armed; a second one moves the empty pool's price to the limit and only then does PriceLimitAlreadyExceeded appear. buyPrio is protected by accident (StakingVault.notifyReward(0) reverts ZeroAmount). Fix: in _swapEthFor `if (out == 0 || spent == 0) revert Slippage();`, and/or set `purchased` only when `spent > 0`.","line":336,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev buyImd against an initialized ETH/IMD pool with no liquidity (several live ETH/IMD pools are exactly\n/// that) exchanges nothing: out = 0, spent = 0. With minImdOut = 0 (what operator/operator.py always passes)\n/// every check passes, the call succeeds, and `purchased` flips to true although nothing was bought, so\n/// setPrio/setSinks become immutable before any money has flowed. Fails on the current code; passes once a\n/// purchase that bought nothing is refused.\ncontract ProofC_ZeroFillTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n    address fakeHook = makeAddr(\"hook\"); // FeeTreasury.receive only checks msg.sender == hook\n    address adapterAddr = makeAddr(\"adapter\");\n\n    PoolManager manager;\n    PrismRiotToken prio;\n    PrismRiotToken imd;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        prio = new PrismRiotToken();\n        imd = new PrismRiotToken();\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(prio));\n        arena = new Arena(owner, address(prio));\n        vm.startPrank(owner);\n        treasury.bindHook(fakeHook);\n        treasury.setPrio(address(prio));\n        treasury.setSinks(address(vault), address(arena), adapterAddr);\n        treasury.setExecutor(executor);\n        treasury.setPriceFloors(1e18, 1e18);\n        treasury.setImd(address(imd));\n        treasury.setImdPool(3000, 60, address(0));\n        vm.stopPrank();\n        vm.deal(fakeHook, 100 ether);\n        vm.prank(fakeHook);\n        (bool ok,) = address(treasury).call{value: 10 ether}(\"\");\n        require(ok);\n        treasury.allocate();\n        // The pool exists but holds no liquidity.\n        manager.initialize(\n            PoolKey(CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(address(imd)), 3000, 60, IHooks(address(0))),\n            SQRT_PRICE_1_1\n        );\n    }\n\n    function test_purchaseThatBuysNothingIsRefused() public {\n        uint256 budgetBefore = treasury.imdBudget();\n        assertFalse(treasury.purchased());\n        vm.prank(executor);\n        vm.expectRevert(); // expected: refused (nothing bought); today it succeeds with out == 0\n        treasury.buyImd(0.1 ether, 0);\n        assertEq(treasury.imdBudget(), budgetBefore);\n        assertFalse(treasury.purchased(), \"no purchase happened, nothing may be frozen\");\n        // The bindings must still be correctable.\n        vm.prank(owner);\n        treasury.setSinks(address(vault), address(arena), adapterAddr);\n    }\n}","reproduction":"State: FeeTreasury configured (hook bound, PRIO, sinks, executor, floors 1e18/1e18, setImd(IMD), setImdPool(3000, 60, 0)), 10 ETH income allocated (imdBudget 2.85 ETH); ETH/IMD pool initialized at 1:1 with zero liquidity. Call: executor buyImd(0.1 ether, 0). Expected: revert (nothing bought). Actual: returns 0; imdBudget unchanged (measured 13432835820895522 before and after in test/scratch/Quantify.t.sol with a smaller income), spentInWindow 0, purchased == true; owner setSinks(vault, arena, adapter) now reverts AlreadySet. Proof: test/scratch/ProofC_ZeroFill.t.sol (fails now with 'next call did not revert as expected').","severity":"low","snippet":"        imdBudget += ethIn - spent;\n        purchased = true;","title":"FeeTreasury.buyImd: a swap that fills nothing (out = 0, spent = 0) is accepted as a purchase and flips `purchased`, freezing setPrio/setSinks before any money has flowed"},{"citation":"resolved","description":"cmd_buy simulates and then broadcasts `buyPrio/buyImd(eth_in, 0)` (lines 227 and 236). The contract's design has two slippage defences, the executor's minOut and the owner's floor, and documents the floor as kept 'a little below the market' (README B5: ~10% below, re-set when prices move). With minOut = 0 the treasury accepts any fill down to the floor, so a public-mempool searcher can push the pool price up to the floor, let the treasury buy, and sell back. Measured in test/scratch/Quantify.t.sol (pool at 1:1, 1.25% LP fee + 0.5% hook fee, floor 10% under spot, treasury buyPrio(0.25 ether, 0)): with pool liquidity L = 20e18 the best push (0.1 ETH) nets the searcher 0.02186 ETH per treasury purchase, 8.7% of the 0.25 ETH spent; with L = 100e18 and L = 1000e18 the round-trip fees exceed the gap and no push is profitable. So the leak exists only while the live ETH/PRIO pool is thin, which is the launch state. The extraction comes out of the PRIO/IMD budgets (fewer tokens for rewards, games and agent work). Fix in the operator: take `out` from the `cast call` simulation and send minOut = out * (1 - tolerance) (e.g. 99%), or at least minOut = floor-derived amount at the current spot; on chain nothing needs to change.","line":236,"path":"operator/operator.py","reproduction":"State: ETH/PRIO pool (hooked) with full-range liquidity 20e18 at ~1:1, FeeTreasury funded by fees and allocated, minPrioPerEth = 0.9 * (honest out / 0.25 ETH). Sequence in one block: searcher buys PRIO with 0.1 ETH; executor buyPrio(0.25 ether, 0) (the operator's exact call) -> succeeds because out is still above the floor; searcher sells all PRIO back. Expected: the treasury's purchase is bounded close to the simulated quote; actual: searcher ends +0.02186 ETH, the treasury received ~8.7% less PRIO than the honest quote. Reproduced by test/scratch/Quantify.t.sol::test_sandwich (scratch, not attached as proof since the fix is in the Python operator, not Solidity).","severity":"low","snippet":"    tx = run([treasury, fn, eth_in, \"0\"], cfg, send=True, dry_run=dry_run)","title":"Operator always sends buyPrio/buyImd with minOut = 0: the owner's static price floor is the only sandwich bound, and on a thin pool a searcher captures most of the 10% floor gap"},{"citation":"resolved","description":"allocate() refills the reserve with up to 10% of each allocation whenever reserve < reserveTarget, and withdrawReserve lets the owner (not the executor) send the whole reserve anywhere with no rate limit. An owner who calls withdrawReserve(owner, reserve) before every allocate() receives 10% of income plus 40% of the remaining 90%: 46% of all fees, while IMD and PRIO each receive 27% instead of the 30% the brief states. Numbers for 1 ETH of income per allocation with reserveTarget 0.5 ETH: toReserve 0.1, rest 0.9 -> IMD 0.27, PRIO 0.27, owner 0.36 + 0.1 drawn from the reserve = 0.46. This is within the documented owner power ('The owner may send it anywhere') and the invariant test caps reserve + reserve withdrawals at 10% of income, so it is reported as a trust assumption, not a defect: the reserve is meant for operator gas, and the 30/30/40 split is only guaranteed on the 90% that never touches the reserve. If the brief's 30/30/40 is meant to hold on gross fees once the reserve is funded, rate-limit the owner's reserve draw too, or stop refilling after a lifetime cap.","line":259,"path":"src/FeeTreasury.sol","reproduction":"State: FeeTreasury with 1 ETH of unallocated income, reserve 0, reserveTarget 0.5 ETH. allocate() -> reserve 0.1, imdBudget 0.27, prioBudget 0.27, ownerBudget 0.36. Owner withdrawReserve(owner, 0.1 ether) -> reserve 0. Next 1 ETH of income, allocate() -> again reserve 0.1, imd +0.27, prio +0.27, owner +0.36. Repeated: owner receives 0.46 of every 1 ETH, IMD and PRIO 0.27 each.","severity":"info","snippet":"        uint256 room = reserve < reserveTarget ? reserveTarget - reserve : 0;\n        if (toReserve > room) toReserve = room;","title":"Trust assumption: the owner can recycle the 10% reserve top-up into a 46/27/27 split by draining the reserve before each allocation"}],"hash":"0ca498656287026b8f6c112361c729ce6d1a4151140e34f0ab5286f643cc8b75","nodeId":"e8a6beac-b0fb-4f75-ad15-24ea19e33946","outcome":"completed","summary":"Review complete. `.imd-findings.json` is written at the repository root with 5 findings, 3 Foundry proofs and 73 coverage rows (all 69 listed entry points answered, plus manifest and invariant rows). No tracked file was changed; only the ignored `test/scratch/` folder holds my working files.\n\n**What I found (economics, invariants, flow gaps)**\n\n1. **Medium. FeeTreasury IMD line redirectable after money has flowed.** `setPrio` and `setSinks` freeze on the first purchase, but `setImd` and `setImdPool` do not, so the owner can point the 30% IMD budget at any token and any pool after purchases began. This contradicts the contract's own notice. Proof: `test/scratch/ProofA_ImdFreeze.t.sol` fails on the current tree.\n2. **Medium. OracleAdapter IMD withdrawable after an asset switch.** `withdrawToken` only blocks the current `asset`. Two owner calls (`setPayment` to another token, then `withdrawToken`) take out the IMD bought for agent work, which the function promises cannot leave. Proof: `test/scratch/ProofB_AdapterWithdraw.t.sol`.\n3. **Low. Zero-fill `buyImd` counts as a purchase.** Against an initialized pool with no liquidity and the operator's hardcoded `minOut = 0`, the swap returns out 0 and spent 0, every check passes, and `purchased` flips, freezing the bindings with no money moved. Proof: `test/scratch/ProofC_ZeroFill.t.sol`.\n4. **Low. Operator sends `minOut = 0` always.** The owner floor is then the only sandwich bound. Measured: on a pool with liquidity 20e18 a searcher nets 0.0219 ETH from a 0.25 ETH treasury buy with a 10% floor gap. At liquidity 100e18 or more the fees exceed the gap and nothing is extractable.\n5. **Info. Owner reserve recycling.** Draining the reserve before each `allocate()` yields an effective 46/27/27 split instead of 40/30/30. Documented owner power, recorded as a trust assumption.\n\n**What held.** The allocation split, bucket conservation, the hook-fee return loop during `buyPrio`, StakingVault principal isolation and funded-only streaming, Arena escrow and prize accounting on every payout branch, the oracle relay rules, and the manifest arguments. I re-ran the full suite (174 tests pass), confirmed the oracle signer against the live API, and confirmed the hook's `treasury()` is still zero on mainnet.\n\n**Not reached in depth.** Hook swap math on partial fills beyond the fee-conservation seam, the live Intake's price and refund behaviour, and the game-theory of players covering all choices, which is a published rule rather than a code defect.","treeHash":null,"usage":{"cachedInputTokens":3303064,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":67644,"runtime":"claude","turns":52,"wallClockMs":1078967}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"aca5d7170d77c721","findings":[{"citation":"resolved","description":"The tree ships launch.json with kind univ4_hook and top-level hook/token/pool objects, and its constructor arguments use $poolManager, $token and $factory. The contracts-only schema accepts exactly kind, contracts and notes, kind must be evm_contracts, every contract entry needs contract + constructorArgs, and the only references allowed are static words, $owner and $contract:EarlierName. A launch.json with an extra top-level key refuses the whole file; a constructor argument of $poolManager/$token has no meaning in this mode. The same placeholders are repeated in README.md's 'Deployment parameters' table (lines 51-54), which the adapter would otherwise copy. The four application constructors need static addresses: FeeTreasury(0x000000000004444c5dc75cb358380d2e3de08a90, 0x13afb9b5780cd9ae79c61503adb69c57845d8eac); StakingVault(0x13afb9b5780cd9ae79c61503adb69c57845d8eac, 0xfd1c234972768c23bb21d655966e0b122dd67a2c); Arena(same two); OracleAdapter(0x13afb9b5780cd9ae79c61503adb69c57845d8eac, 0x5598aa9146215bc13eb26f2c692ad1461fd32982). The brief states the owner address, so it is written as that static address rather than $owner. The signer was checked today against api.imd.fun/oracle/requests, whose top-level attester field and every attested mainnet request carry 0x5598aa9146215bc13eb26f2c692ad1461fd32982. None of the four constructors calls another contract, each takes two arguments, and all runtimes are under EIP-170 (Arena 8922, FeeTreasury 9661, OracleAdapter 10576, StakingVault 3788 bytes), so a correctly written manifest should pass the protected floor.","line":2,"path":"launch.json","reproduction":"State: launch.json at commit 34e992a as shipped. Input: submit it for an evm_contracts launch. Expected: a manifest listing exactly FeeTreasury, StakingVault, Arena, OracleAdapter with static constructor addresses. Actual: kind is univ4_hook, top-level keys hook/token/pool are not schema keys, constructorArgs contain $poolManager/$token/$factory which do not resolve in contracts-only mode; the file is refused as a whole (and if any worker left it in a build tree it would be checked as a manifest and reject the work).","severity":"medium","snippet":"  \"kind\": \"univ4_hook\",","title":"launch.json in the tree is the previous univ4_hook manifest, not an evm_contracts manifest; it must be replaced, not adapted"},{"citation":"resolved","description":"submitAttestation is permissionless and _accept binds a result to the round only through questionHash, chainId, panel/quorum minimums and the pinned signer. It never ties the attestation to a request this adapter made (pendingRound/pendingSince are consulted only on the Intake callback path, and even there a.requestId is not compared with intakeId). request() also has no 'already pending' check (line 201 only refuses once a result is settled), so the executor may pay for several panel answers for one round. The question body is public on chain from pinQuestion, and the IMD oracle accepts paid requests from anyone (api.imd.fun/oracle/requests lists requests from many parties with the same signer 0x5598aa91...). Consequently: (1) if the panel answers the same question twice with different answers (a reasoning panel is not deterministic), whoever relays first fixes the winning choice, and the honest operator's later relay reverts AlreadySettled; a player holding a losing commitment can keep buying answers (0.5 IMD each, the live Intake price for action oracle.request@oracle-1) until one matches their commitment and front-run the operator's relay. (2) A third party's request for the same body can be answered and published by the oracle before the round's commitDeadline, so the NatSpec claim that 'nothing is public while commitments are still open' holds only for this adapter's own requests. The Arena then pays the prize to the wrong party; principal is unaffected. Minimal fix preserving the permissionless relay: accept only attestations whose requestId the adapter itself registered (store the intake request id per round in request() and require a.requestId == that id or pendingRound[a.requestId] == roundId, if the Intake uses the attestation requestId as its id; otherwise restrict submitAttestation to executor/owner and keep the Intake callback as the open path), and refuse request() while one request for the round is pending.","line":246,"path":"src/OracleAdapter.sol","reproduction":"Reproduced in test/scratch/Leads.t.sol test_competingAttestationsFirstRelayWins: pin question Q for round 1 (notBefore in the past), executor calls request(1) twice (both succeed, 1.0 IMD leaves the adapter), then two attestations by the pinned signer with questionHash Q, requestIds r1/r2 and answers 1 and 2 are signed. submitAttestation(1, a2) from an arbitrary address stores answer 2; submitAttestation(1, a1) afterwards reverts AlreadySettled(1). Expected: only the answer to the adapter's own request for the round can settle it, and a second request for an already requested round is refused. Actual: first relay wins, duplicate requests spend budget.","severity":"medium","snippet":"        _accept(roundId, a, signature);","title":"Any attestation for the pinned question settles a round: a second attestation (other requester, or a repeated executor request) lets the first relayer choose the answer"},{"citation":"resolved","description":"The three one-shot setters accept any non-zero address and can never be corrected, unlike TreasuryFeeHook.bindTreasury, which validates the counterpart and stays correctable until the first delivery. buyPrio needs stakingVault.notifyReward and arena.fundPrizes to succeed; if the sinks are swapped, point at a vault for a different token, or at a contract without those functions, every buyPrio reverts forever and nothing else can ever spend prioBudget, so 30% of all future income is frozen in the treasury (the ETH is not stolen, but it can never reach staking or games). Likewise bindHook with the wrong hook makes receive() refuse every fee delivery (the real hook keeps ETH in pendingEth; a new treasury must be deployed, which is only possible because hook.bindTreasury is still correctable). The brief asks the owner to prepare these bindings by hand. Minimal fix: in setSinks staticcall StakingVault.prio()/rewardFunder-independent checks (prio() == prio, and Arena.prio() == prio, OracleAdapter.oracleSigner() != 0), and in bindHook check IFeeHook(hook_).poolKey().currency1 == prio once prio is set; or allow correction until the first successful buyPrio, mirroring bindTreasury.","line":159,"path":"src/FeeTreasury.sol","reproduction":"State: fresh FeeTreasury with hook and prio bound, prioBudget = 1 ether after allocate(). Input: owner calls setSinks(arena, vault, adapter) (vault and arena swapped). Then executor calls buyPrio(0.1 ether, 0). Expected: either the misconfiguration is refused or correctable. Actual: setSinks succeeds and emits SinksSet; buyPrio reverts because IRewardSink(arena).notifyReward does not exist on Arena; a second setSinks reverts AlreadySet; prioBudget stays unspendable permanently.","severity":"low","snippet":"        if (stakingVault != address(0)) revert AlreadySet();","title":"setSinks, bindHook and setPrio are irrevocable without any interface check: one wrong address strands the 30% PRIO budget forever"},{"citation":"resolved","description":"NatSpec (line 37-38 'at most spendPerWindow ETH per rolling SPEND_WINDOW') and README/OPERATOR.md ('per rolling day') describe a rolling limit, but the code resets spentInWindow to zero whenever a full SPEND_WINDOW has elapsed since windowStart. The real bound is 2 x spendPerWindow in any 24h span, and the two spends can be one second apart. This halves the protection the owner sizes against a compromised executor key. Minimal fix: document the bucket semantics, or track the last spend timestamp and decay.","line":300,"path":"src/FeeTreasury.sol","reproduction":"State: spendPerWindow = 1 ether, maxSpendPerSwap = 1 ether, prioBudget >= 2 ether, windowStart = W after an earlier purchase. Input: executor calls buyPrio(1 ether, ...) at timestamp W + 86399 (spentInWindow becomes 1 ether), then buyPrio(1 ether, ...) at W + 86400. Expected under a rolling window: the second call reverts ExceedsWindow until W + 86399 + 86400. Actual: at W + 86400 the branch resets windowStart and spentInWindow = 0, and the second 1 ether purchase succeeds: 2 ether spent within one second.","severity":"low","snippet":"        if (block.timestamp >= windowStart + SPEND_WINDOW) {","title":"Spend window is a fixed 24h bucket, not the rolling window the NatSpec and README promise: 2x spendPerWindow can be spent within one second"},{"citation":"resolved","description":"The executor (the server hot key) can call withdrawReserve(to, amount) for any 'to' and for the whole reserve, with no per-window cap and no fixed destination. The contract's own claim (line 38-39: a compromised executor key is bounded in rate) therefore does not cover the reserve: a stolen key drains up to reserveTarget (<= 2 ETH) immediately and every later reserve top-up (10% of each allocation until the target is reached) as it arrives, until the owner notices and calls setExecutor(0). The brief asks for a bounded executor. Minimal fix: let the executor withdraw only to itself (to == executor) and cap it per SPEND_WINDOW, keeping the owner path unrestricted.","line":245,"path":"src/FeeTreasury.sol","reproduction":"State: reserve = 0.5 ether, executor = E. Input: attacker holding E's key calls withdrawReserve(attacker, 0.5 ether). Expected: refused (wrong destination) or limited. Actual: 0.5 ETH leaves to attacker; after each later allocate() the attacker repeats with the new reserve amount. No window check exists in withdrawReserve.","severity":"low","snippet":"        if (msg.sender != owner() && msg.sender != executor) revert NotExecutor();","title":"withdrawReserve lets the executor send the whole reserve to any address with no rate limit, unlike purchases"},{"citation":"resolved","description":"Arena.createRound always uses roundId = roundCount + 1 and requires oracle.pinned(roundId).notBefore == commitDeadline with commitDeadline > block.timestamp. A pin for roundCount + 1 whose notBefore is already in the past (or 0) can never be matched, and the pin cannot be replaced or cleared. The only recovery is deploying a new OracleAdapter and Arena.setOracle to it, which also means repinning every future question there. Minimal fix: let the owner re-pin or clear a round id that the Arena has not consumed yet (Arena.roundCount() < roundId), or let the Arena accept an explicit roundId with a skip.","line":171,"path":"src/OracleAdapter.sol","reproduction":"Reproduced in test/scratch/Leads.t.sol test_badPinBlocksRoundCreation: pinQuestion(1, Q, 1, 5, 4, notBefore = now - 1, body). createRound(..., commitDeadline = now + 1 day, ...) reverts QuestionNotPinned; createRound with commitDeadline = now - 1 reverts BadDeadlines; pinQuestion(1, ...) again reverts AlreadyPinned(1). Expected: a way to correct the pin before the round exists. Actual: round 1, and therefore every later round, cannot be created on this adapter.","severity":"low","snippet":"        if (_pinned[roundId].questionHash != bytes32(0)) revert AlreadyPinned(roundId);","title":"pinQuestion is immutable and roundCount+1 is forced: a single wrong notBefore pin blocks all round creation on the adapter"},{"citation":"resolved","description":"Checked on Ethereum mainnet at block 26154398 via public RPC and Blockscout. Transaction 0x545df1adb27c4a2ad6de57dd3d4d28005306f1471c0002381d5518fbc1c6dd7d (block 26154170, from 0xcecc29b0..., to factory 0x12C63b581d07093F6126bc02263c58f7EadaA96F, status success) created exactly three contracts: PRIO 0xfd1c234972768c23bb21d655966e0b122dd67a2c, hook 0x65a783cc6725a02ce349dc4d72577994df1760cc and a distributor 0xd5873749535E5e558e3c2181ff8E9F43E4fc2927. PRIO runtime equals out/PrismRiotToken deployedBytecode byte for byte; hook runtime equals out/TreasuryFeeHook deployedBytecode outside its three immutables. Hook: owner 0x13AFB9b5780cd9Ae79c61503Adb69c57845d8EAc, pendingOwner 0, token PRIO, poolManager 0x000000000004444c5dc75cB358380D2e3dE08A90, factory as above, initialized true, poolKey (ETH, PRIO, 12500, 60, hook), treasury 0, pendingEth 0, pendingClaims 0, totalFeeCharged 0, FEE_BPS 50. Pool slot0: sqrtPriceX96 = 10000 * 2^96, tick 184216, lpFee 12500; PoolManager holds 869,999,999.99 PRIO for it. The owner wallet has sent no transaction since block 26154147 and the hook has none, so no FeeTreasury/StakingVault/Arena/OracleAdapter has been deployed or bound: deploying the four is not a duplicate. IMD 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 (18 decimals), Intake 0x1397434cd35e8a9c8ac312a61d3a285eb31dea56 exposes the selectors request(bytes32,bytes,(address,bytes4),address,uint256) and priceOf(bytes32,address); priceOf(oracle.request@oracle-1, IMD) = 0.5 IMD, matching README step 10. Oracle attester today: 0x5598aa9146215bc13eb26f2c692ad1461fd32982 (api.imd.fun/oracle/requests). README lines 18-28 and the launch.json notes still say nothing is deployed; the adapter should update them.","line":24,"path":"README.md","reproduction":"Input: cast call 0x65a783cc6725a02ce349dc4d72577994df1760cc 'treasury()(address)' on mainnet. Expected per README: no contract deployed. Actual: the hook exists, returns 0x0 (unbound), and PRIO exists; README status text is outdated.","severity":"info","snippet":"- **No contract has been deployed.** This assignment does not control a funded wallet or keys; the","title":"Live records verified: PRIO and hook match this source and are owned by the project owner; no application contract exists yet; README's deployment status is stale"}],"hash":"30741c6316f8c1594d0dd6323e2ad4f0bb310d3a411508f994487ba62d25420d","nodeId":"41a4aeac-1957-4e03-accc-cfac4fa3621b","outcome":"completed","summary":"The review is complete. Seven findings are in `.imd-findings.json` (two medium, four low, one info); nothing critical or high surfaced, so no proof tests were attached.\n\n**Scope read in full:** FeeTreasury, StakingVault, Arena, OracleAdapter, OracleAttestation, TwoStepOwned, TreasuryFeeHook, PrismRiotToken, the deploy script, launch.json, README, docs and the operator's request/relay code. The existing suite passes (164 tests, including invariants). Nothing in scope was unreachable. Live mainnet records were verified over public RPC and Blockscout.\n\n**Live state confirmed**\n- PRIO and the hook bytecode match this commit's build exactly (hook outside its three immutables).\n- Hook owner is the project wallet, treasury is unbound, zero fees charged so far, pool initialized at 1e8 PRIO per ETH with 870M PRIO in the PoolManager.\n- The launch transaction created only PRIO, the hook and a distributor. The owner has sent no transaction since, so no FeeTreasury, StakingVault, Arena or OracleAdapter exists. Deploying the four is not a duplicate.\n- Oracle signer 0x5598aa91…2982 is the attester reported by the IMD oracle API today. Intake 0x1397…ea56 exposes the request and priceOf selectors the adapter calls, and quotes 0.5 IMD for the configured action.\n\n**Findings**\n- **Medium, launch.json:** the shipped manifest is the old univ4_hook one with `$poolManager`/`$token`/`$factory` placeholders and hook/token/pool keys. It must be rewritten as evm_contracts with static addresses. The README deployment table repeats the same placeholders.\n- **Medium, OracleAdapter manual relay:** any signed attestation for the pinned question settles the round. Request has no pending check, and anyone can buy the same question from the IMD oracle, so a second attestation with a different answer lets the first relayer pick the outcome. Reproduced in the scratch test.\n- **Low, FeeTreasury one-shot setters:** setSinks, bindHook and setPrio are irrevocable with no interface check. One wrong address freezes the 30% PRIO budget permanently.\n- **Low, FeeTreasury spend window:** it is a fixed bucket, not rolling, so double the window limit can be spent one second apart.\n- **Low, withdrawReserve:** the executor can send the whole reserve to any address with no rate cap.\n- **Low, pinQuestion:** an immutable pin with a past notBefore for the next round id blocks all round creation until a new adapter is deployed. Reproduced in the scratch test.\n- **Info:** README still says nothing is deployed. It should be updated with the verified records above.\n\nOne lead was checked and dropped. The Intake callback still fits its gas stipend with cold storage, so no finding there.","treeHash":null,"usage":{"cachedInputTokens":2652893,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":52513,"runtime":"claude","turns":53,"wallClockMs":779935}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"759c614fdc84ff66","findings":[{"citation":"resolved","description":"Merged: audit_permissions fa9d1d69 and audit_flow b5ed4c35 (same root cause). _replaceable() decides whether a pin is still free by asking the *current* `arena` for roundCount(), and setArena (line 172) is an owner setter with no one-shot guard. Arena.settle (src/Arena.sol:297-300) trusts r.oracle.resultOf(roundId) and never compares the stored r.questionHash or the pinned signer with what the adapter holds. So the guarantee in OracleAdapter NatSpec ('Once the Arena has created the round the pin is immutable'), Arena NatSpec lines 40-43 and README 'Frozen before entry' does not hold: after players have committed and revealed, the owner rotates the signer to a key they hold, points `arena` at any contract whose roundCount() is 0, re-pins the open round with a new question and that signer, relays a self-signed attestation (owner is always an allowed relayer) and settles the round on an answer of their choosing. Prize shares and the 10 PRIO wrong-choice penalties of the players go to the side the owner picks. Owner-only, hence medium: a privileged actor bypasses an explicitly stated, enforced-looking immutability of player-funded rounds. The same breakage also happens by honest mistake if setArena is pointed at a redeployed Arena with fewer rounds. Fix preserving the correction window: make setArena one-shot (revert when arena != address(0)) or record per round id that the pin was consumed, and/or have the Result carry the question hash so Arena.settle can compare it with r.questionHash.","line":213,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {Arena, IRoundOracle} from \"src/Arena.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Reports roundCount() == 0 forever: what the owner points `OracleAdapter.setArena` at.\ncontract EmptyArenaStub {\n    function roundCount() external pure returns (uint256) {\n        return 0;\n    }\n}\n\n/// @dev The adapter promises that a pin is immutable once the Arena has created its round, and the Arena\n/// promises that nothing about an open round's result source can change. `setArena` is re-settable and\n/// `_replaceable` trusts whatever it points at, so the owner can re-pin an OPEN round's question and signer\n/// and settle it with an attestation signed by a key of their choosing.\ncontract RepinOpenRoundTest is Test {\n    uint256 constant SIGNER_KEY = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;\n    address constant SIGNER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;\n    bytes32 constant QUESTION = keccak256(\"round question\");\n    uint64 constant T0 = 1_800_000_000;\n\n    PrismRiotToken token;\n    Arena arena;\n    OracleAdapter adapter;\n    address owner = makeAddr(\"owner\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n    uint256 rogueKey;\n    address rogueSigner;\n\n    function setUp() public {\n        vm.warp(T0);\n        (rogueSigner, rogueKey) = makeAddrAndKey(\"rogue\");\n        token = new PrismRiotToken();\n        arena = new Arena(owner, address(token));\n        adapter = new OracleAdapter(owner, SIGNER);\n        vm.startPrank(owner);\n        arena.setOracle(IRoundOracle(address(adapter)));\n        adapter.setArena(address(arena));\n        vm.stopPrank();\n        token.transfer(alice, 1_000 ether);\n        token.transfer(bob, 1_000 ether);\n        vm.prank(alice);\n        token.approve(address(arena), 102 ether);\n        vm.prank(bob);\n        token.approve(address(arena), 102 ether);\n        token.approve(address(arena), type(uint256).max);\n        arena.fundPrizes(1_000 ether);\n    }\n\n    function attestation(uint256 answer, bytes32 question, uint64 issuedAt)\n        internal\n        view\n        returns (OracleAttestation.Attestation memory a)\n    {\n        a = OracleAttestation.Attestation({\n            requestId: keccak256(abi.encode(\"req\", answer, question)),\n            chainId: 1,\n            questionHash: question,\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(answer),\n            figure: 0,\n            fromBlock: 1,\n            toBlock: 2,\n            blockHash: bytes32(uint256(1)),\n            panelJobId: keccak256(\"job\"),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: issuedAt,\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function sign(uint256 key, OracleAttestation.Attestation memory a) internal view returns (bytes memory) {\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, adapter.attestationDigest(a));\n        return abi.encodePacked(r, s, v);\n    }\n\n    function test_openRoundPinCannotBeReplacedThroughSetArena() public {\n        uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n        vm.startPrank(owner);\n        adapter.pinQuestion(1, QUESTION, 1, 5, 4, commitDeadline, \"\");\n        uint256 id = arena.createRound(\n            Arena.Mode.FactionDuel, 2, commitDeadline, commitDeadline + 1 hours, commitDeadline + 2 hours, 300 ether, 0, 0\n        );\n        vm.stopPrank();\n        assertEq(id, 1);\n\n        // Alice picks 2, Bob picks 1; both reveal.\n        bytes32 saltA = keccak256(\"a\");\n        bytes32 saltB = keccak256(\"b\");\n        bytes32 cA = arena.commitmentOf(id, alice, 2, saltA);\n        bytes32 cB = arena.commitmentOf(id, bob, 1, saltB);\n        vm.prank(alice);\n        arena.enter(id, cA);\n        vm.prank(bob);\n        arena.enter(id, cB);\n        skip(1 hours);\n        vm.prank(alice);\n        arena.reveal(id, 2, saltA);\n        vm.prank(bob);\n        arena.reveal(id, 1, saltB);\n        skip(1 hours);\n\n        // The owner tries to change the open round's result source: rotate the signer to a key they hold,\n        // point the adapter at a contract that reports no rounds, and re-pin round 1.\n        bytes32 rogueQuestion = keccak256(\"rogue question\");\n        vm.startPrank(owner);\n        adapter.setSigner(rogueSigner);\n        (bool arenaSwapped,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.setArena, (address(new EmptyArenaStub()))));\n        (bool repinned,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.pinQuestion, (id, rogueQuestion, 1, 5, 4, commitDeadline, \"\"))\n        );\n        vm.stopPrank();\n        arenaSwapped; // whether this step is refused or the re-pin is, the pin must survive\n\n        // Expected: the pin the round opened with is untouched.\n        assertFalse(repinned, \"a pin consumed by an open round must not be replaceable\");\n        assertEq(adapter.pinned(id).questionHash, QUESTION, \"question hash changed on an open round\");\n        assertEq(adapter.pinned(id).signer, SIGNER, \"signer changed on an open round\");\n\n        // Expected: an answer signed by the owner's key for the owner's question cannot settle the round.\n        OracleAttestation.Attestation memory rogue = attestation(0, rogueQuestion, commitDeadline); // winning = 1\n        bytes memory rogueSig = sign(rogueKey, rogue);\n        vm.prank(owner);\n        (bool settledByRogue,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.submitAttestation, (id, rogue, rogueSig)));\n        assertFalse(settledByRogue, \"owner-signed answer accepted for an open round\");\n        assertFalse(adapter.resultOf(id).settled);\n\n        // The real signer's answer still settles it (sanity: the honest path is intact).\n        OracleAttestation.Attestation memory real = attestation(1, QUESTION, commitDeadline); // winning = 2\n        bytes memory realSig = sign(SIGNER_KEY, real);\n        vm.prank(owner);\n        adapter.submitAttestation(id, real, realSig);\n        arena.settle(id);\n        assertEq(arena.payoutOf(id, alice), 400 ether);\n        assertEq(arena.payoutOf(id, bob), 90 ether);\n    }\n}","reproduction":"Ran test/scratch/Proof_fa9d1d69658b.t.sol (forge test --match-path): Arena.setOracle(adapter), adapter.setArena(arena); owner pins round 1 (QUESTION, signer S, notBefore T) and createRound(FactionDuel, 2, T, T+1h, T+2h, 300 PRIO); Alice enters/reveals 2, Bob 1. Owner: setSigner(rogue); setArena(new EmptyArenaStub()) [roundCount()==0]; pinQuestion(1, rogueQuestion, 1,5,4, T, ''). Expected AlreadyPinned(1); actual: succeeds, pinned(1).questionHash==rogueQuestion, signer==rogue. Owner then submitAttestation(1, rogue-signed answer 0) is accepted -> settle pays Bob (winning choice 1). Test fails today with 'a pin consumed by an open round must not be replaceable'.","severity":"medium","snippet":"        return IArenaRounds(arena).roundCount() < roundId;","title":"OracleAdapter: re-pointing setArena makes the pinned question and signer of an already-open Arena round replaceable, so the owner can pick its winner"},{"citation":"resolved","description":"From write_foundry_tests 52223cf0. _accept() (line 302) stores a result for any pinned round once block.timestamp >= notBefore, without checking that the Arena has created the round. Once stored, _replaceable() returns false because _results[roundId].settled, so the owner can no longer re-pin the id; and Arena.createRound requires p.notBefore == commitDeadline with block.timestamp < commitDeadline, which is impossible for a lapsed notBefore. Round ids are sequential (roundCount + 1), so no further round can be opened against this adapter. Trigger: the owner pins round N (as README 'per round' instructs: pin, then create) and the commit deadline passes before createRound is mined (a delayed or forgotten tx — the very case the replaceable-pin fix was added for), then the executor relays any bought attestation for that question or runs operator 'request-round' (operator.py cmd_request_round checks only notBefore, not that the round exists), after which anyone can relay the answer or the Intake callback stores it. Severity medium, not high: the owner can recover by pointing Arena.setOracle at a separately deployed adapter/helper for that one id and back; but the FeeTreasury oracleAdapter sink is frozen after the first purchase, so the documented re-pin recovery path is lost and a non-launch contract deployment is needed. Fix: refuse request()/_accept for roundId > IArenaRounds(arena).roundCount() when arena is set (or require arena set), or let _replaceable ignore/clear a result for an id the Arena has not consumed.","line":212,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {Arena, IRoundOracle} from \"src/Arena.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Proof: a result stored on the adapter for a round id the Arena has not created yet can never be\n/// undone, and that id can never be created afterwards. Round ids are sequential, so the Arena can never\n/// open another round. The executor (a bounded hot-wallet role) can trigger it on its own with one relay\n/// of a bought attestation, once a pin's `notBefore` has passed and the owner has not yet created the round.\n///\n/// Fails on the current code at \"the owner can still open the pinned round\". A fix that refuses to store a\n/// result for an uncreated round, or that lets the owner replace such a pin (and its result) while the Arena\n/// has not created the round, makes it pass: the executor's relay below is a low-level call whose outcome\n/// is not asserted.\ncontract ProofUncreatedRoundResultBricksArena is Test {\n    uint256 constant SIGNER_KEY = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;\n    bytes32 constant QUESTION = keccak256(\"round 1 question\");\n\n    PrismRiotToken prio;\n    Arena arena;\n    OracleAdapter adapter;\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        prio = new PrismRiotToken();\n        arena = new Arena(owner, address(prio));\n        adapter = new OracleAdapter(owner, vm.addr(SIGNER_KEY));\n        vm.startPrank(owner);\n        arena.setOracle(IRoundOracle(address(adapter)));\n        adapter.setArena(address(arena));\n        adapter.setExecutor(executor);\n        vm.stopPrank();\n    }\n\n    function test_resultForAnUncreatedRoundMustNotBrickRoundCreation() public {\n        // The owner pins round 1 for a commit deadline one hour out, planning to create the round later.\n        uint64 notBefore = uint64(block.timestamp + 1 hours);\n        vm.prank(owner);\n        adapter.pinQuestion(1, QUESTION, 1, 5, 4, notBefore, \"\");\n        assertEq(arena.roundCount(), 0, \"round 1 does not exist yet\");\n\n        // The boundary passes before the owner creates the round. The executor relays a bought attestation\n        // for round 1 (the executor may relay any attestation; nothing checks that the round exists).\n        vm.warp(notBefore);\n        OracleAttestation.Attestation memory a = OracleAttestation.Attestation({\n            requestId: keccak256(\"bought off chain\"),\n            chainId: 1,\n            questionHash: QUESTION,\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(uint256(2)),\n            figure: 0,\n            fromBlock: 1,\n            toBlock: 2,\n            blockHash: 0,\n            panelJobId: 0,\n            panelSize: 5,\n            quorum: 4,\n            agreed: 4,\n            issuedAt: notBefore,\n            expiresAt: notBefore + 1 days\n        });\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, adapter.attestationDigest(a));\n        vm.prank(executor);\n        (bool relayed,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.submitAttestation, (1, a, abi.encodePacked(r, s, v)))\n        );\n        relayed; // a fixed adapter may refuse this; the property below is what matters\n\n        // The owner must still be able to open round 1: either by re-pinning it for a new commit deadline\n        // (the pin is for a round the Arena has not created, so the audit fix says it is replaceable) ...\n        uint64 newDeadline = uint64(block.timestamp + 1 hours);\n        vm.prank(owner);\n        (bool repinned,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.pinQuestion, (1, QUESTION, 1, 5, 4, newDeadline, \"\"))\n        );\n        // ... or by creating it against the pin as it stands.\n        uint64 pinnedDeadline = adapter.pinned(1).notBefore;\n        uint64 commitDeadline = repinned ? newDeadline : pinnedDeadline;\n        bool creatable = block.timestamp < commitDeadline;\n        if (creatable) {\n            vm.prank(owner);\n            (bool created,) = address(arena).call(\n                abi.encodeCall(\n                    Arena.createRound,\n                    (\n                        Arena.Mode.VaultRaid,\n                        4,\n                        commitDeadline,\n                        commitDeadline + 1 hours,\n                        commitDeadline + 2 hours,\n                        0,\n                        0,\n                        bytes32(0)\n                    )\n                )\n            );\n            creatable = created;\n        }\n        assertTrue(creatable, \"the owner can still open the pinned round\");\n        assertEq(arena.roundCount(), 1, \"round 1 opened; the Arena is not stuck at round 0 forever\");\n    }\n}","reproduction":"Ran test/scratch/Proof_52223cf0f335.t.sol: setOracle/setArena/setExecutor; owner pinQuestion(1, Q, 1,5,4, now+1h, ''); roundCount()==0. warp(now+1h). executor submitAttestation(1, {Q, issuedAt notBefore, uint256 answer 2, panel 5/4/4} signed by pinned signer) -> stored. Owner pinQuestion(1, Q, ..., now+1h) -> reverts AlreadyPinned(1) (expected: replaceable, the round was never created); createRound with commitDeadline==pinned notBefore -> BadDeadlines (notBefore is not in the future). Test fails today at 'the owner can still open the pinned round'.","severity":"medium","snippet":"        if (arena == address(0) || _results[roundId].settled || openRequest[roundId] != bytes32(0)) return false;","title":"OracleAdapter: a result stored for a pinned round the Arena has not created yet makes that round id impossible to create through the adapter"},{"citation":"resolved","description":"Merged: audit_permissions fd703ec7 and audit_economics 4e2d3905. withdrawToken's NatSpec says the IMD bought from fees for agent work 'cannot be withdrawn: it is spent only on panel answers', but the guard compares against the mutable `asset`, which setPayment (line 153) can change at any time. Three owner calls move the whole IMD balance to any address and restore the configuration, while FeeTreasury keeps forwarding the IMD line to this frozen sink. The brief fixes the economy at 30% IMD work / 30% PRIO / 40% owner with no owner advances; this turns the 30% IMD line into owner income. README's trust section mentions that renaming the asset makes the old one withdrawable, but the contract's own NatSpec claims the opposite and nothing in the design needs it. Fix: remember every token ever configured as asset (mapping set in setPayment) and refuse it in withdrawToken, or make the asset one-shot while price stays settable.","line":348,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\n\n/// @dev `withdrawToken` refuses only the token that is `asset` at call time, and `setPayment` can move `asset`\n/// at any time. So the IMD bought for agent work from the 30% allocation is withdrawable by the owner in\n/// three calls, contrary to the \"AssetNotWithdrawable\" guarantee and the brief's economics.\ncontract WithdrawImdTest is Test {\n    address owner = makeAddr(\"owner\");\n    address signer = makeAddr(\"signer\");\n    OracleAdapter adapter;\n    PrismRiotToken imd;\n    PrismRiotToken other;\n\n    function setUp() public {\n        adapter = new OracleAdapter(owner, signer);\n        imd = new PrismRiotToken();\n        other = new PrismRiotToken();\n        vm.prank(owner);\n        adapter.setPayment(address(imd), 0.5 ether);\n        // IMD the treasury bought from fees and handed over for panel answers.\n        imd.transfer(address(adapter), 5 ether);\n    }\n\n    function test_configuredImdCannotBeWithdrawnByRotatingTheAsset() public {\n        vm.startPrank(owner);\n        // Direct withdrawal is refused, as documented.\n        vm.expectRevert(OracleAdapter.AssetNotWithdrawable.selector);\n        adapter.withdrawToken(address(imd), owner, 5 ether);\n        // Rotate the asset away, withdraw, rotate it back.\n        (bool rotated,) = address(adapter).call(abi.encodeCall(OracleAdapter.setPayment, (address(other), 1)));\n        (bool withdrawn,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.withdrawToken, (address(imd), owner, 5 ether)));\n        address(adapter).call(abi.encodeCall(OracleAdapter.setPayment, (address(imd), 0.5 ether)));\n        vm.stopPrank();\n        rotated;\n        // Expected: the IMD bought for agent work stays in the adapter whatever the owner does.\n        assertFalse(withdrawn, \"IMD left the adapter through withdrawToken\");\n        assertEq(imd.balanceOf(address(adapter)), 5 ether, \"IMD for panel answers was withdrawn\");\n        assertEq(imd.balanceOf(owner), 0);\n    }\n}","reproduction":"Ran test/scratch/Proof_fd703ec7323f.t.sol: OracleAdapter(owner, signer); owner setPayment(IMD, 0.5e18); adapter holds 5 IMD. withdrawToken(IMD, owner, 5e18) -> AssetNotWithdrawable (as documented). setPayment(OTHER, 1); withdrawToken(IMD, owner, 5e18) -> expected AssetNotWithdrawable, actual succeeds (adapter IMD 0, owner +5 IMD); setPayment(IMD, 0.5e18) restores. Test fails today at 'IMD left the adapter through withdrawToken'.","severity":"medium","snippet":"        if (token == asset) revert AssetNotWithdrawable();","title":"OracleAdapter.withdrawToken: the IMD bought from the 30% agent-work line can be withdrawn by rotating `asset` with setPayment"},{"citation":"resolved","description":"Merged: audit_math ac433839, audit_flow 02a96f4d, audit_economics 99ebf9c1 (and the related write_foundry_tests info 30d4020e on buyPrio, which is protected only because StakingVault.notifyReward(0) reverts ZeroAmount). When the configured ETH/IMD pool has no IMD to sell below the current price (a drained pool, or a wrong fee/tickSpacing in B7 that selects one of the six empty ETH/IMD pools listed in docs/DEPLOYMENT.md section 4), PoolManager.swap walks to sqrtPriceLimit MIN_SQRT_PRICE+1 and returns a zero delta without reverting. unlockCallback settles 0 and takes 0; with minOut = 0 (what operator.py always sends) the check is 0 < 0 || 0 < 0, so buyImd continues: purchased = true (line 337), safeTransfer(oracleAdapter, 0), ImdBought(0,0). Consequences: (1) the FeeTreasury guarantee that PRIO and sinks are correctable 'until the first purchase' is broken by a call that bought nothing; (2) the pool is left at MIN_SQRT_PRICE+1, so the next buy reverts PriceLimitAlreadyExceeded; (3) the operator's simulate-first path sees success, broadcasts, and calls backoff.clear — the opposite of the brief's 'back off until liquidity returns'. docs/DEPLOYMENT.md section 4 also states exhaustion reverts PriceLimitAlreadyExceeded, which is only true for the second call. Fix: in _swapEthFor revert when spent == 0 || out == 0, and set purchased only for a non-zero fill.","line":359,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev A `buyImd` against an ETH/IMD pool whose IMD side is exhausted fills nothing (spent 0, out 0), yet is\n/// accepted as a purchase: the floor check `out < spent * floor / 1e18` is `0 < 0`, `purchased` flips to true\n/// (freezing `setPrio` / `setSinks` for ever), `ImdBought(0, 0)` is emitted and the pool is left parked at\n/// MIN_SQRT_PRICE + 1 so the next buy reverts `PriceLimitAlreadyExceeded`.\n/// Expected: a swap that spends nothing and returns nothing is refused and `purchased` stays false.\ncontract BuyImdZeroFillTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n    address hookStandIn = makeAddr(\"hook\"); // `receive()` only checks the sender; any address can be bound\n    address adapterStandIn = makeAddr(\"adapter\");\n\n    PoolManager manager;\n    PrismRiotToken prio;\n    PrismRiotToken imd;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        prio = new PrismRiotToken();\n        imd = new PrismRiotToken();\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(prio));\n        arena = new Arena(owner, address(prio));\n\n        // An initialized ETH/IMD pool (same fee / tick spacing as the plan's B7) with no IMD to sell.\n        PoolKey memory imdKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 10_000,\n            tickSpacing: 200,\n            hooks: IHooks(address(0))\n        });\n        manager.initialize(imdKey, SQRT_PRICE_1_1);\n\n        vm.startPrank(owner);\n        treasury.bindHook(hookStandIn);\n        treasury.setPrio(address(prio));\n        treasury.setSinks(address(vault), address(arena), adapterStandIn);\n        treasury.setImd(address(imd));\n        treasury.setImdPool(10_000, 200, address(0));\n        treasury.setPriceFloors(1e8 ether, 1e3 ether); // 1e8 PRIO per ETH, 1000 IMD per ETH\n        treasury.setExecutor(executor);\n        vm.stopPrank();\n\n        // Fee income, then allocation: imdBudget = 30% of the post-reserve remainder (2.85 ETH here).\n        vm.deal(hookStandIn, 10 ether);\n        vm.prank(hookStandIn);\n        (bool ok,) = address(treasury).call{value: 10 ether}(\"\");\n        require(ok, \"income refused\");\n        treasury.allocate();\n        assertGt(treasury.imdBudget(), 0.1 ether);\n    }\n\n    function test_zeroFillIsNotAPurchase() public {\n        assertFalse(treasury.purchased());\n        uint256 budgetBefore = treasury.imdBudget();\n\n        // The operator (operator/operator.py cmd_buy) sends minOut = 0 and relies on the owner's floor. A zero\n        // fill passes that floor, so the contract must refuse on its own: nothing was bought.\n        vm.prank(executor);\n        vm.expectRevert();\n        treasury.buyImd(0.1 ether, 0);\n\n        assertFalse(treasury.purchased(), \"a zero fill must not freeze PRIO and the sinks\");\n        assertEq(treasury.imdBudget(), budgetBefore);\n        assertEq(imd.balanceOf(adapterStandIn), 0);\n    }\n}","reproduction":"Ran test/scratch/Proof_ac4338397d76.t.sol: PoolManager with an ETH/IMD pool (fee 10000, spacing 200, no hooks) initialized at 1:1 with no liquidity; treasury bindHook/setPrio/setSinks/setImd/setImdPool(10000,200,0)/setPriceFloors(1e26,1e21)/setExecutor; 10 ETH income allocated (imdBudget 2.85 ETH). executor buyImd(0.1 ether, 0): expected revert and purchased()==false; actual returns 0, purchased()==true, imdBudget unchanged. Test fails today with 'next call did not revert as expected'.","severity":"medium","snippet":"        if (out < minOut || out < (spent * floorPerEth) / ONE) revert Slippage();","title":"FeeTreasury.buyImd accepts a zero fill (spent 0, out 0) as a purchase: sets `purchased`, freezes setPrio/setSinks, parks the pool at the price limit and defeats the operator's PriceLimitAlreadyExceede"},{"citation":"resolved","description":"Merged: audit_permissions 7e308245 (low) and audit_economics 9e5adfb1 (medium). setPrio and setSinks check `if (purchased) revert AlreadySet()`; setImd (175-181) and setImdPool (235-246) do not. After purchases the owner can point the IMD line at any token and any PoolKey, including one with an owner-controlled hook or an owner-supplied liquidity position, set minImdPerEth to 1 and let buyImd spend the IMD budget there, recovering the ETH as LP. The contract notice (lines 55-57) says the opposite. Kept at low: owner-only, README's trust section lists 'the IMD token and venue (setImd/setImdPool)' as remaining owner powers, and moving the venue when liquidity migrates is a legitimate need; the defect is the contradictory on-chain guarantee. Fix: freeze `imd` with `purchased` as for PRIO, and after `purchased` accept only hooks == address(0) pool keys (or document the notice as covering PRIO only).","line":175,"path":"src/FeeTreasury.sol","reproduction":"Code trace: after any successful buyPrio/buyImd (purchased == true), owner setPrio(x) -> AlreadySet and setSinks(...) -> AlreadySet, but owner setImd(0xANY) succeeds (no purchased check at lines 175-181) and setImdPool(3000, 60, 0xOwnerHook) succeeds (lines 235-246); the next executor buyImd(ethIn, 0) swaps imdBudget ETH in that pool with only the owner-set floor as bound.","severity":"low","snippet":"    function setImd(address imd_) external onlyOwner {","title":"FeeTreasury.setImd / setImdPool stay mutable after the first purchase, contradicting the notice that the 30% IMD allocation can never be redirected once money has flowed"},{"citation":"resolved","description":"From write_foundry_tests c4ba685f. The vault and arena are checked against prio(), the third address is only checked for zero. All three are frozen by the shared `purchased` flag, which buyPrio sets even if no IMD has ever moved. A typo in step A3's third argument becomes permanent at the first buyPrio; from then on buyImd can only deliver IMD to the wrong address, and imdBudget has no other spending path. Fix: validate the adapter (e.g. require it to report the same owner or an `asset()`/marker), or freeze oracleAdapter only on the first buyImd.","line":195,"path":"src/FeeTreasury.sol","reproduction":"Code trace: owner bindHook, setPrio, setSinks(vault, arena, 0xTYPO) (accepted: line 188 only rejects zero), executor buyPrio(0.1 ether, 1) -> purchased = true (line 315). Owner setSinks(vault, arena, rightAdapter) -> reverts AlreadySet. Expected: correctable until IMD has flowed to the adapter or validated at A3; actual: permanent before any IMD purchase.","severity":"low","snippet":"        oracleAdapter = oracleAdapter_;","title":"FeeTreasury.setSinks does not validate the OracleAdapter sink, and a PRIO-only purchase freezes it"},{"citation":"resolved","description":"Merged: audit_permissions 8321c273 and audit_flow 47eaa772. The verification table and ADAPTATION.md line 18 record pendingEth() = 0, and the checklist presents bindTreasury (A4) and bindHook (A1) as correctable until the first fee is delivered. The live hook already holds fee ETH, and TreasuryFeeHook.flush() is permissionless: immediately after A4, anyone can deliver it, which sets totalFeeDelivered != 0 (bindTreasury frozen, TreasuryFeeHook.sol:181) and FeeTreasury.totalIncome != 0 (bindHook frozen, FeeTreasury.sol:160). There is effectively no correction window; a wrong A4 destination (an EOA passes the hook() staticcall check) would lose all fees forever. Fix (docs only, live hook cannot change): state the current pendingEth, and require before signing A4 a `cast call <treasury> 'hook()(address)'` returning the hook (A1 mined) and a code check of the A4 argument.","line":13,"path":"docs/DEPLOYMENT.md","reproduction":"Re-read during this review: cast call 0x65a783cc6725a02ce349dc4d72577994df1760cc 'pendingEth()(uint256)' --rpc-url https://ethereum-rpc.publicnode.com at block 26154706 returns 390361348266782 (specialists saw 99502487562190 at ~26154575); treasury() = 0, totalFeeDelivered() = 0, owner nonce 163 (no application contracts yet). Sequence: owner signs A4; any address calls hook.flush() in the same/next block -> ETH delivered -> bindTreasury(other) reverts TreasuryAlreadyBound, bindHook(other) reverts HookAlreadyBound.","severity":"low","snippet":"| TreasuryFeeHook | `0x65a783cc6725a02ce349dc4d72577994df1760cc` | `owner()` = `0x13AFB9b5…8EAc`, `pendingOwner()` = 0, `token()` = PRIO, `poolManager()` = `0x0000…8a90`, `factory()` = `0x12C63b58…A96F`, `initialized()` = true, `FEE_BPS()` = 50, `treasury()` = **0**, `pendingEth()` = 0 |","title":"Deployment record is stale: the live hook already holds pending fee ETH, so A1/A4 become permanent in the first block after A4"},{"citation":"resolved","description":"From audit_economics be80fbf2. The contract offers two slippage bounds (executor minOut and the owner's floor). The operator simulates with minOut 0 (line 227) and sends with minOut 0 (line 236) instead of using the simulated output, so any fill down to the owner's floor is accepted. A public-mempool searcher can push the price toward the floor before the executor's tx and sell back after; the loss comes out of the PRIO/IMD budgets. It matters while the ETH/PRIO pool is thin (the launch state). Fix in the operator: take `out` from the simulation and send minOut = out * (1 - tolerance), or send through a private relay.","line":236,"path":"operator/operator.py","reproduction":"Code trace: cmd_buy builds [treasury, 'buyPrio(uint256,uint256)', eth_in, '0'] for both the simulation and the send. With minPrioPerEth set 10% below spot (README B5), a searcher buy that moves the price by <10% before the executor's tx leaves out >= spent*floor/1e18, so _swapEthFor (FeeTreasury.sol:359) accepts the worse fill; the searcher sells back after. Expected: the treasury's fill bounded near the simulated quote; actual: bounded only by the floor.","severity":"low","snippet":"    tx = run([treasury, fn, eth_in, \"0\"], cfg, send=True, dry_run=dry_run)","title":"Operator sends buyPrio/buyImd with minOut = 0, leaving only the owner's static floor (~10% under market) as sandwich protection"},{"citation":"resolved","description":"From write_foundry_tests 8ac0a634. cancel() needs block.timestamp >= resultDeadline + 72h; nothing caps resultDeadline. If an owner creates a round with a far-future resultDeadline and no result is ever stored, every entrant's 102 PRIO stays in escrow indefinitely (no owner path can take it, but no player path returns it). README says no owner power can reach Arena escrow. Owner-set and visible before entry, hence low. Fix: cap resultDeadline - commitDeadline (e.g. 30 days).","line":189,"path":"src/Arena.sol","reproduction":"Owner pins round 1 at T=now+1h and createRound(VaultRaid, 4, T, T+1h, type(uint64).max - 1 days, 0, 0, 0) -> accepted. Alice enters (102 PRIO). No result stored. cancel(1) reverts NotCancellable for any realistic timestamp, refund(1) reverts NotCancelled, payoutOf(1, alice) == 0.","severity":"low","snippet":"        if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {","title":"Arena.createRound has no upper bound on resultDeadline, so a round can hold player escrow with no reachable cancel path"},{"citation":"resolved","description":"From audit_permissions 90e7e80a. Strangers may relay only an attestation answering an open request() made by the executor. With no open request (paid operations disabled at launch, or a cleared stale request) only the executor or owner can store a result, so they decide between settlement and the permissionless cancel 72h after resultDeadline; the owner may also be a player. Intentional design (prevents answer shopping by players); documented here as a trust assumption.","line":289,"path":"src/OracleAdapter.sol","reproduction":"Round 1 open, no request() made. Stranger submitAttestation(1, correctly signed attestation, sig) -> NotRelayer (pendingSince[a.requestId] == 0). After resultDeadline + 72h, cancel(1) succeeds; winners get 102 instead of 100 + prize share.","severity":"info","snippet":"        } else if (msg.sender != executor && msg.sender != owner()) {","title":"Trust assumption: permissionless settlement depends on the executor having made a paid request; otherwise owner/executor choose settle vs cancel by relaying or withholding"},{"citation":"resolved","description":"From audit_economics 63e4b5f9. The owner's withdrawReserve is not rate-limited; allocate() refills the reserve with up to 10% of each allocation while below target. An owner who empties the reserve before every allocation receives 10% + 40%*90% = 46% of fees; IMD and PRIO get 27% each. Within the documented owner power ('The owner may send it anywhere'); recorded so the brief's 30/30/40 is understood to apply after the reserve top-up.","line":284,"path":"src/FeeTreasury.sol","reproduction":"reserve 0, reserveTarget 0.5 ETH, 1 ETH income: allocate() -> reserve 0.1, imd 0.27, prio 0.27, owner 0.36. Owner withdrawReserve(owner, 0.1 ether). Repeat per 1 ETH of income: owner nets 0.46, IMD/PRIO 0.27 each.","severity":"info","snippet":"    function withdrawReserve(address payable to, uint256 amount) external nonReentrant {","title":"Trust assumption: owner reserve withdrawals are unlimited, so draining the reserve before each allocate() shifts the split to 46/27/27"}],"hash":"45d0440248caa0296a3da7a1be751a4f249ee6cec8e5ecb78cf189c636d8d9b2","nodeId":"aa562353-5ca8-4ed5-a1a3-24de6c7da0c0","outcome":"completed","summary":"The review is finished and `.imd-findings.json` is written: 11 findings (4 medium, 5 low, 2 info) and a coverage row for all 69 entry points plus one row for the manifest. I changed no tracked files; the scratch copies of the proofs were removed afterwards.\n\n**Test runs**\n- The project's suite passes: 187 of 187 tests.\n- All four proofs the specialists attached fail on the current code for the reason their finding states. Each is attached to its finding below.\n\n**Findings kept** (duplicates across specialists merged)\n1. **Medium – re-pointing `setArena` reopens a live round's question.** The check for whether a question can still be replaced asks whichever Arena `setArena` currently points at, and the owner can change that at any time. So the owner can swap the question and signer of a round players have already entered, sign an answer themselves and decide the winner. Reported by the permissions and flow specialists; proof attached.\n2. **Medium – an answer stored before a round is created blocks that round.** If a question's commit deadline passes before `createRound` is mined, the executor can store an answer for it. After that the question can't be replaced and the round can't be created through this adapter. The owner can still recover by pointing the Arena at a separately deployed adapter, so I rated it medium, not high. Proof attached.\n3. **Medium – the owner can withdraw the IMD bought for agent work.** `withdrawToken` only refuses the payment token currently set, so `setPayment(other)` → `withdrawToken(IMD)` → `setPayment(IMD)` moves the whole 30% IMD line to the owner. Two specialists reported it; proof attached.\n4. **Medium – `buyImd` treats a swap that buys nothing as a purchase.** Against an empty or wrongly configured IMD pool it returns 0 without reverting. It still freezes the PRIO and sink settings, parks the pool at the price limit and clears the operator's back-off. Three specialists reported it; proof attached. `docs/DEPLOYMENT.md` §4 also wrongly says an exhausted pool reverts on the first call.\n5. **Low – `setImd`/`setImdPool` are never frozen,** although the contract notice says the IMD line can't be redirected once money has flowed. The README lists this as an owner power, hence low.\n6. **Low – `setSinks` doesn't check the OracleAdapter address,** and a PRIO-only purchase freezes it.\n7. **Low – the deployment record is out of date.** I re-read the live hook at block 26154706: it holds 390361348266782 wei of pending fees, not the recorded 0. Anyone can deliver those fees as soon as the hook is pointed at the treasury (step A4), which permanently fixes both bindings, so there is no window to correct a mistake.\n8. **Low – the operator always sends `minOut = 0`,** leaving the owner's floor, set about 10% below market, as the only protection against sandwich trades.\n9. **Low – `resultDeadline` has no upper limit,** so an unanswered round can keep player escrow with no reachable cancel.\n10. **Info – trust note:** strangers can relay an answer only after the executor has paid for a request, so without one the owner or executor decides between settling and cancelling a round.\n11. **Info – trust note:** the owner can draw the reserve without limit before each allocation, which turns the 30/30/40 split into 46/27/27 in the owner's favour.\n\n**Dropped:** the Intake callback gas-margin note (informational only, and I didn't re-measure it). The `buyPrio` dust-fill note is folded into finding 4.\n\n**Manifest:** `launch.json` matches the schema. Constructor arguments are in declaration order, the owner is the static address the brief gives, and the notes are 3,282 characters. I didn't independently re-verify the oracle signer address against the protocol's API.","treeHash":null,"usage":{"cachedInputTokens":2710453,"inputTokens":46,"model":"claude-opus-5-5","outputTokens":34607,"runtime":"claude","turns":24,"wallClockMs":412466}},{"artifacts":[],"attempt":1,"bundleHash":"64e43971f8b93f9974c8d0778a92b849b4793c05e3c2f3522bfbedafe0ed6fbb","device":"077d2937780a81bc","findings":[],"hash":"5125d7bff4e22e263f7aada80c0570ee74833e2e79f9c1d3eb6267174c8fe9ba","nodeId":"803731c0-5e6d-4e31-911d-38cffe857ce7","outcome":"completed","summary":"Everything verified. Wrapping up.\n\n**Delivered:** `launch.json` at the repository root, the only changed file (git status shows it as the sole addition). It names the four application contracts in dependency order with two static address arguments each.\n\n| Contract | constructorArgs |\n| --- | --- |\n| FeeTreasury | PoolManager `0x0000…8a90`, owner `0x13af…8eac` |\n| StakingVault | owner, PRIO `0xfd1c…7a2c` |\n| Arena | owner, PRIO |\n| OracleAdapter | owner, oracle signer `0x5598…2982` |\n\n**Decisions worth knowing**\n- **Owner written as the static address, not `$owner`.** The brief names the wallet and the reference says a brief-stated owner is written statically. This also matches the accepted ADAPTATION.md and ConfigPlan constants. It is the live hook's owner, re-read on chain.\n- **Oracle signer** was re-read today from the IMD oracle API. Its top-level attester and the signer of every attested request is the same address the manifest carries.\n- **No duplicates exist.** At block 26154559 the hook's treasury is still zero, the original launch receipt succeeded, and the owner wallet has created no contract since. The owner account carries an EIP-7702 delegation but is the same wallet.\n\n**Verification**\n- Manifest shape checked with jq: exactly the three schema keys, string arguments, valid identifiers, notes 3282 characters.\n- Each contract's argument count and types match the compiled ABI's constructor.\n- `forge build` and `forge test`: 174 tests passed across 20 suites, including the launch adaptation test that simulates the whole owner configuration plan.\n- The protected floor test was run in test/scratch with the exact creation code plus manifest arguments: 1 passed. The addresses it printed used arbitrary salts and are not predictions of live addresses.\n\n**Not done, by design:** nothing was deployed, signed or broadcast, and no addresses, receipts or ABIs beyond the ones already in `docs/abi/` were produced. Those come from the deployer step. The owner's post-launch transactions remain prepared in `script/ConfigPlan.s.sol` and `docs/DEPLOYMENT.md`, with the permanent hook binding last in phase A. I did not commit; the task asked only for the file.","treeHash":"f59f6ecebdd1b63a8bafb7aa6dedea5558ab3075","usage":{"cachedInputTokens":354597,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":9342,"runtime":"claude","turns":18,"wallClockMs":211896}},{"artifacts":[],"attempt":1,"bundleHash":"cda6b1739dfb7ffb69933b919caa3b167b605eaac0082eb19520204f4a50052b","device":"aca5d7170d77c721","findings":[{"description":"Arena round ids are sequential (`roundId = roundCount + 1`) and `createRound` requires `block.timestamp < commitDeadline` with `pinned(roundId).notBefore == commitDeadline`. The adapter accepts and stores a result for any pinned round as soon as `block.timestamp >= notBefore` (via `submitAttestation` by the executor or owner, or via the Intake callback / any relayer answering an executor-made `request()`), with no check that the Arena has created that round. Once a result is stored, `_replaceable()` returns false (`_results[roundId].settled`), so the owner cannot re-pin the id, and its `notBefore` is now in the past, so the owner cannot create it either. Round `roundCount + 1` can never be created and the Arena can never open another round; `unallocatedPrizePool` (the games' half of every PRIO purchase) has no owner withdrawal path and is stranded, and `FeeTreasury.setSinks` is frozen after the first purchase so the treasury keeps feeding it. The executor is the bounded hot-wallet role the brief asks to keep bounded; it needs only a pin whose `notBefore` has passed before the owner created the round (an owner that pins and creates in separate transactions, or a mistaken pin the audit fix was meant to let the owner replace) plus one bought attestation, or one `request()` for that id.","line":287,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {Arena, IRoundOracle} from \"src/Arena.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Proof: a result stored on the adapter for a round id the Arena has not created yet can never be\n/// undone, and that id can never be created afterwards. Round ids are sequential, so the Arena can never\n/// open another round. The executor (a bounded hot-wallet role) can trigger it on its own with one relay\n/// of a bought attestation, once a pin's `notBefore` has passed and the owner has not yet created the round.\n///\n/// Fails on the current code at \"the owner can still open the pinned round\". A fix that refuses to store a\n/// result for an uncreated round, or that lets the owner replace such a pin (and its result) while the Arena\n/// has not created the round, makes it pass: the executor's relay below is a low-level call whose outcome\n/// is not asserted.\ncontract ProofUncreatedRoundResultBricksArena is Test {\n    uint256 constant SIGNER_KEY = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;\n    bytes32 constant QUESTION = keccak256(\"round 1 question\");\n\n    PrismRiotToken prio;\n    Arena arena;\n    OracleAdapter adapter;\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        prio = new PrismRiotToken();\n        arena = new Arena(owner, address(prio));\n        adapter = new OracleAdapter(owner, vm.addr(SIGNER_KEY));\n        vm.startPrank(owner);\n        arena.setOracle(IRoundOracle(address(adapter)));\n        adapter.setArena(address(arena));\n        adapter.setExecutor(executor);\n        vm.stopPrank();\n    }\n\n    function test_resultForAnUncreatedRoundMustNotBrickRoundCreation() public {\n        // The owner pins round 1 for a commit deadline one hour out, planning to create the round later.\n        uint64 notBefore = uint64(block.timestamp + 1 hours);\n        vm.prank(owner);\n        adapter.pinQuestion(1, QUESTION, 1, 5, 4, notBefore, \"\");\n        assertEq(arena.roundCount(), 0, \"round 1 does not exist yet\");\n\n        // The boundary passes before the owner creates the round. The executor relays a bought attestation\n        // for round 1 (the executor may relay any attestation; nothing checks that the round exists).\n        vm.warp(notBefore);\n        OracleAttestation.Attestation memory a = OracleAttestation.Attestation({\n            requestId: keccak256(\"bought off chain\"),\n            chainId: 1,\n            questionHash: QUESTION,\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(uint256(2)),\n            figure: 0,\n            fromBlock: 1,\n            toBlock: 2,\n            blockHash: 0,\n            panelJobId: 0,\n            panelSize: 5,\n            quorum: 4,\n            agreed: 4,\n            issuedAt: notBefore,\n            expiresAt: notBefore + 1 days\n        });\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, adapter.attestationDigest(a));\n        vm.prank(executor);\n        (bool relayed,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.submitAttestation, (1, a, abi.encodePacked(r, s, v)))\n        );\n        relayed; // a fixed adapter may refuse this; the property below is what matters\n\n        // The owner must still be able to open round 1: either by re-pinning it for a new commit deadline\n        // (the pin is for a round the Arena has not created, so the audit fix says it is replaceable) ...\n        uint64 newDeadline = uint64(block.timestamp + 1 hours);\n        vm.prank(owner);\n        (bool repinned,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.pinQuestion, (1, QUESTION, 1, 5, 4, newDeadline, \"\"))\n        );\n        // ... or by creating it against the pin as it stands.\n        uint64 pinnedDeadline = adapter.pinned(1).notBefore;\n        uint64 commitDeadline = repinned ? newDeadline : pinnedDeadline;\n        bool creatable = block.timestamp < commitDeadline;\n        if (creatable) {\n            vm.prank(owner);\n            (bool created,) = address(arena).call(\n                abi.encodeCall(\n                    Arena.createRound,\n                    (\n                        Arena.Mode.VaultRaid,\n                        4,\n                        commitDeadline,\n                        commitDeadline + 1 hours,\n                        commitDeadline + 2 hours,\n                        0,\n                        0,\n                        bytes32(0)\n                    )\n                )\n            );\n            creatable = created;\n        }\n        assertTrue(creatable, \"the owner can still open the pinned round\");\n        assertEq(arena.roundCount(), 1, \"round 1 opened; the Arena is not stuck at round 0 forever\");\n    }\n}","reproduction":"1. Owner: `adapter.setArena(arena)`, `adapter.pinQuestion(1, q, 1, 5, 4, T+1h, body)`; Arena.roundCount() == 0. 2. Warp to T+1h. 3. Executor: `adapter.submitAttestation(1, attestation{questionHash q, issuedAt T+1h, uint256 answer}, sig by the pinned signer)` -> stored, `resultOf(1).settled == true`. 4. Owner: `adapter.pinQuestion(1, q, 1, 5, 4, T+2h, body)` -> reverts `AlreadyPinned(1)` (expected: replaceable, the Arena never created round 1). 5. Owner: `arena.createRound(VaultRaid, 4, T+2h, T+3h, T+4h, 0, 0, 0)` -> reverts `QuestionNotPinned` (pin notBefore is T+1h); any `commitDeadline == T+1h` reverts `BadDeadlines`. Expected: the owner can still open round 1. Actual: round 1 and therefore every later round can never be created. Fix options that preserve the design: refuse `_accept`/`request` for `roundId > IArenaRounds(arena).roundCount()` when `arena` is set, or let `_replaceable` ignore a stored result for an id the Arena has not consumed and clear it on re-pin.","severity":"medium","title":"A result stored for a round id the Arena has not created yet permanently blocks all future rounds"},{"description":"`setSinks` validates the vault and arena against PRIO (`IPrioSink.prio()`), but the third address (`oracleAdapter_`) is not checked against anything, and all three are frozen by the shared `purchased` flag, which `buyPrio` sets. A typo in the adapter address at step A3 becomes permanent the moment the executor buys PRIO, before any IMD has ever moved. From then on `buyImd` can only deliver IMD to the wrong address, and the only alternative is to never spend the IMD budget (30% of all income minus reserve), which then accumulates with no exit (no owner path spends `imdBudget` except `buyImd`). The ADAPTATION.md rationale for finding 0cd78a87 (\"a wrong sink used to be permanent from the first call\") is only partly met: the adapter sink is still permanent from an unrelated first call.","line":186,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {Hooks} from \"v4-core/src/libraries/Hooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {TreasuryFeeHook} from \"src/TreasuryFeeHook.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\n\n/// @dev Proof (low): the OracleAdapter sink is never checked against anything and is frozen by the first\n/// *PRIO* purchase, which does not involve it. A typo in the adapter address at step A3 therefore becomes\n/// permanent as soon as the executor buys PRIO, and the 30% IMD line can then only ever be sent to the wrong\n/// address (or sit unspent forever). Expected: the adapter sink stays correctable until IMD has actually\n/// flowed to it (or is validated at A3). Actual: `setSinks` reverts `AlreadySet` after a PRIO-only purchase.\ncontract ProofAdapterSinkFrozenByPrioPurchase is Test {\n    uint160 constant HOOK_FLAGS = Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG\n        | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG;\n\n    address owner = makeAddr(\"owner\");\n    address factory = makeAddr(\"factory\");\n    address executor = makeAddr(\"executor\");\n    PoolManager manager;\n    PrismRiotToken token;\n    TreasuryFeeHook hook;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n    PoolKey key;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        vm.prank(factory);\n        token = new PrismRiotToken();\n        address at = address((uint160(uint256(keccak256(\"hook\"))) & ~uint160(0x3FFF)) | HOOK_FLAGS);\n        bytes memory creation = abi.encodePacked(\n            type(TreasuryFeeHook).creationCode, abi.encode(IPoolManager(address(manager)), address(token), factory, owner)\n        );\n        vm.etch(at, creation);\n        (bool ok, bytes memory runtime) = at.call(\"\");\n        require(ok);\n        vm.etch(at, runtime);\n        hook = TreasuryFeeHook(payable(at));\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(token));\n        arena = new Arena(owner, address(token));\n        key = PoolKey(CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, 79228162514264337593543950336);\n        PoolModifyLiquidityTest lp = new PoolModifyLiquidityTest(IPoolManager(address(manager)));\n        vm.deal(factory, 100_000 ether);\n        vm.startPrank(factory);\n        token.approve(address(lp), type(uint256).max);\n        lp.modifyLiquidity{value: 20_000 ether}(\n            key, ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10_000 ether, 0), \"\"\n        );\n        vm.stopPrank();\n    }\n\n    function test_adapterSinkStaysCorrectableUntilImdHasFlowedToIt() public {\n        address wrongAdapter = makeAddr(\"typo\");\n        address rightAdapter = makeAddr(\"adapter\");\n        vm.startPrank(owner);\n        hook.bindTreasury(payable(address(treasury)));\n        treasury.bindHook(address(hook));\n        treasury.setPrio(address(token));\n        treasury.setSinks(address(vault), address(arena), wrongAdapter); // A3 with a typo in the third address\n        vault.setRewardFunder(address(treasury));\n        treasury.setExecutor(executor);\n        treasury.setPriceFloors(1, 1);\n        vm.stopPrank();\n        // Fee income from the hook, allocated; the executor buys PRIO only. No IMD has ever been bought.\n        vm.deal(address(hook), 1 ether);\n        vm.prank(address(hook));\n        (bool ok,) = address(treasury).call{value: 1 ether}(\"\");\n        require(ok);\n        treasury.allocate();\n        vm.prank(executor);\n        treasury.buyPrio(0.1 ether, 1);\n        assertEq(treasury.imdBudget(), 0.27 ether, \"the IMD line is untouched\");\n        assertFalse(treasury.imdPoolSet(), \"no IMD purchase was even possible\");\n        // The owner notices the typo and corrects the adapter sink before any IMD moves.\n        vm.prank(owner);\n        (bool corrected,) = address(treasury).call(\n            abi.encodeCall(FeeTreasury.setSinks, (address(vault), address(arena), rightAdapter))\n        );\n        assertTrue(corrected, \"the adapter sink is correctable while no IMD has flowed to it\");\n        assertEq(treasury.oracleAdapter(), rightAdapter);\n    }\n}","reproduction":"1. Owner: bindHook, setPrio, `setSinks(vault, arena, 0xTYPO)`, setExecutor, setPriceFloors. 2. Hook delivers 1 ETH; `allocate()`; executor `buyPrio(0.1 ether, 1)` (IMD pool not even configured, `imdBudget` untouched at 0.27 ETH). 3. Owner: `setSinks(vault, arena, rightAdapter)` -> reverts `AlreadySet`. Expected: correctable until IMD has flowed to the adapter (or validated at A3, e.g. `OracleAdapter.owner() == owner()` / `asset()`), actual: permanent. Mitigation until fixed: triple-check the third A3 argument against the launch record before the first `buyPrio`, exactly as docs/DEPLOYMENT.md already warns for A3/A4.","severity":"low","title":"OracleAdapter sink is unvalidated and frozen by a PRIO-only purchase, stranding the 30% IMD line on a typo"},{"description":"`createRound` only checks `commitDeadline < revealDeadline < resultDeadline`. `cancel` requires `block.timestamp >= resultDeadline + 72h`. A round created with `resultDeadline` far in the future (up to type(uint64).max) whose question is never answered keeps every entrant's 102 PRIO escrowed indefinitely; the owner cannot take it (there is no owner withdrawal from escrow, which is correct) but nothing returns it to players either. This is an owner-trust matter, not a theft path, but the README's trust section says none of the owner's remaining powers can reach Arena escrow; locking it is adjacent. A maximum span (for example `resultDeadline - commitDeadline <= 30 days`) would make the refund path a guarantee instead of a convention.","line":189,"path":"src/Arena.sol","reproduction":"Owner pins and creates a round with commitDeadline = now+1h, revealDeadline = now+2h, resultDeadline = type(uint64).max - 1 day. Alice enters (102 PRIO). No result is ever stored. `arena.cancel(id)` reverts `NotCancellable` forever; `arena.refund(id)` reverts `NotCancelled`; `payoutOf(id, alice) == 0`. Expected: a bounded time after which players can always recover 102 PRIO; actual: unbounded.","severity":"low","title":"Arena rounds have no upper bound on resultDeadline, so a round can hold player escrow with no cancel path"},{"description":"When a swap fills so little that `out / 2 == 0` (a one-wei output, or a zero fill that passes a floor of 1 with `spent * floor / 1e18 == 0`), `buyPrio` calls `StakingVault.notifyReward(0)` which reverts `ZeroAmount`, so the whole purchase reverts. Nothing is lost (the revert undoes the swap) and the operator's simulate-first path catches it, so this is a liveness note only: a non-zero fill of 1 wei is refused rather than forwarded. Not reported as a defect; recorded so the operator does not mistake it for a pool problem.","line":318,"path":"src/FeeTreasury.sol","reproduction":"Thin PRIO-only range exhausted so the pool returns 1 wei of PRIO for the ETH taken; `buyPrio(ethIn, 0)` with floor 1 reverts `StakingVault.ZeroAmount()` instead of succeeding with 0 to the vault and 1 to the arena.","severity":"info","title":"buyPrio reverts on dust fills (out < 2 wei of PRIO) because StakingVault.notifyReward(0) refuses"}],"hash":"57c17e145167eb49fdf4124cc1b05959eaa01636c8f25315ea9b59f51518f8f0","nodeId":"ec603212-3511-40c5-890b-29d3bcb1875f","outcome":"completed","summary":"Done. The suite passes and the review surfaced one defect worth reopening the implementation for.\n\n**Outcome**\n\n- `forge build` and `forge test` pass: 187 tests, 0 failures (174 existing plus 13 new), with scratch excluded as the verifier does.\n- Four findings are reported in `.imd-findings.json`, two with self-contained proof tests that fail on the current code (verified) and pass once fixed.\n\n**Defects found, not tested around**\n\n- **Medium, OracleAdapter and Arena:** a result stored for a round id the Arena has not created yet bricks round creation forever. Ids are sequential, a pin with a result cannot be replaced, and its boundary is now in the past, so `createRound` can never satisfy the pin. The executor alone can trigger it with one relayed attestation once a pin's boundary passes before the owner creates the round. The games' half of every PRIO purchase then has no exit. Proof included.\n- **Low, FeeTreasury:** the adapter sink at step A3 is never validated and is frozen by the first PRIO purchase, which does not involve it. A typo there strands the 30% IMD line. Proof included.\n- **Low, Arena:** no upper bound on `resultDeadline`, so an unanswered round can hold player escrow with no refund path. Owner-trust matter, reported for the record.\n- **Info:** a dust fill of under 2 wei makes `buyPrio` revert through the vault's zero-amount check. Nothing is lost.\n\n**Tests added under `test/`**\n\n- `utils/Replica.sol`: a local copy of the mainnet records at their real addresses (PoolManager, PRIO minted to the factory, the hook with its constructor run in place so the flag check is real, the pool at the launch price with PRIO-only liquidity, IMD, an Intake stand-in, the IMD pool the plan names). The four contracts are created from creation code with the manifest's static arguments by the factory address.\n- `LaunchRehearsal.t.sol` (10 tests): ownership lands on the stated owner and not the factory; zero arguments refused; swapped manifest arguments deploy fine but are caught at phase A before anything is permanent; the plan's calldata matches the deployment document byte for byte and no step touches a live asset; A4 before A1 leaves fees waiting in the hook with nothing lost; A4 refuses a treasury bound elsewhere; phase A is idempotent before money moves and frozen after; paid operations stay off after the plan until fees fund them; a full lifecycle (fees via the claim path, 10/30/30/40 split, both purchases, a staker paid the whole stream, a round settled on a paid Intake answer, withdrawals, reconciliation); and the executor's bounds under the plan's defaults including a price manipulation refused by the floor.\n- `LaunchRehearsalInvariants.t.sol`: the wired system under random trades, deliveries, allocations, purchases, withdrawals, staking and oracle requests. Invariants: ETH fully bucketed, income conservation across both doors, each allocation line reconciles over history, purchases land where the brief says, principal and escrow isolated, executor caps hold.\n- `StakingVaultInvariants.t.sol`: per-staker principal exactness, balance equals principal plus owed plus donations exactly, accrued never exceeds owed, the rate never promises more than owed, withdrawal and claim liveness asserted in the handler.\n- `OracleAdapterInvariants.t.sol`: IMD leaves only as the price of a request, results immutable once stored, open-request bookkeeping consistent both ways, consumed ids never reused, strangers never relay foreign attestations.\n\n**One observation for the owner:** the launch pool is small in ETH terms (the whole PRIO supply is worth about 10 ETH at the launch price), so a 9e7 PRIO per ETH floor refuses purchases after about one ETH of buys. The checklist's \"set floors from the current market\" step matters in practice, and the tests model it that way.\n\nFiles are left uncommitted for the harness to collect, with nothing installed and nothing outside `test/` modified.","treeHash":"831f54971a4de790c0cc1ae51a9bd436f83018c4","usage":{"cachedInputTokens":4565099,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":117638,"runtime":"claude","turns":68,"wallClockMs":1510017}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c0fc4ea4f50e3380","findings":[{"citation":"resolved","description":"Seam: boundary x invariant x precision. The only post-swap acceptance test in _swapEthFor is the slippage line above. When the ETH/IMD pool has no IMD to sell (liquidity only above the current tick, or already drained by other buyers with a tighter price limit than MIN_SQRT_PRICE+1), the Uniswap v4 swap moves the price down to the limit with a (0, 0) delta: unlockCallback returns out = 0, owed = 0, settle{value: 0} and take(..., 0) both succeed. Back in _swapEthFor the check is `0 < minOut || 0 < (0 * floor) / 1e18`; the operator (operator/operator.py line 236) always sends minOut = 0 and relies on the owner's price floor, and the floor term is 0 * floor / 1e18 = 0, so `0 < 0` is false and nothing reverts. buyImd then runs `purchased = true;` (line 337), `imd.safeTransfer(oracleAdapter, 0)` and emits ImdBought(0, 0). Consequences: (1) the NatSpec guarantee at line 91 ('True once a purchase has succeeded: PRIO and the sinks are frozen from then on') and lines 55-57 ('correctable until ... the first purchase') are broken: setPrio and setSinks become permanently immutable although no token was ever bought, so a wrong vault/arena/adapter binding can no longer be corrected and the 30% PRIO / 30% IMD budgets would be stuck exactly as audit finding 0cd78a87 described; (2) the pool is left at sqrtPrice 4295128740 = MIN_SQRT_PRICE + 1, so every following buyImd reverts at the PoolManager with PriceLimitAlreadyExceeded (0x7c9c6e8f) until someone sells IMD into the pool; (3) the operator's simulate-first path sees a successful call, sends the transaction (about 270k gas spent for nothing), records a success and clears the price-limit backoff (`backoff.clear(kind)`), the opposite of what the brief asks for (back off until liquidity returns rather than wasting gas). buyPrio is not affected the same way only by accident: with out = 0 it calls StakingVault.notifyReward(0), which reverts ZeroAmount, so that path reverts (gas wasted, no state). Minimal fix preserving the design: in _swapEthFor refuse an empty fill, e.g. `if (spent == 0 || out == 0) revert Slippage();` before the floor comparison (optionally also require out >= minOut with minOut >= 1 in the operator). This keeps partial fills (spent > 0) exactly as documented.","line":359,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev A `buyImd` against an ETH/IMD pool whose IMD side is exhausted fills nothing (spent 0, out 0), yet is\n/// accepted as a purchase: the floor check `out < spent * floor / 1e18` is `0 < 0`, `purchased` flips to true\n/// (freezing `setPrio` / `setSinks` for ever), `ImdBought(0, 0)` is emitted and the pool is left parked at\n/// MIN_SQRT_PRICE + 1 so the next buy reverts `PriceLimitAlreadyExceeded`.\n/// Expected: a swap that spends nothing and returns nothing is refused and `purchased` stays false.\ncontract BuyImdZeroFillTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n    address hookStandIn = makeAddr(\"hook\"); // `receive()` only checks the sender; any address can be bound\n    address adapterStandIn = makeAddr(\"adapter\");\n\n    PoolManager manager;\n    PrismRiotToken prio;\n    PrismRiotToken imd;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        prio = new PrismRiotToken();\n        imd = new PrismRiotToken();\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(prio));\n        arena = new Arena(owner, address(prio));\n\n        // An initialized ETH/IMD pool (same fee / tick spacing as the plan's B7) with no IMD to sell.\n        PoolKey memory imdKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 10_000,\n            tickSpacing: 200,\n            hooks: IHooks(address(0))\n        });\n        manager.initialize(imdKey, SQRT_PRICE_1_1);\n\n        vm.startPrank(owner);\n        treasury.bindHook(hookStandIn);\n        treasury.setPrio(address(prio));\n        treasury.setSinks(address(vault), address(arena), adapterStandIn);\n        treasury.setImd(address(imd));\n        treasury.setImdPool(10_000, 200, address(0));\n        treasury.setPriceFloors(1e8 ether, 1e3 ether); // 1e8 PRIO per ETH, 1000 IMD per ETH\n        treasury.setExecutor(executor);\n        vm.stopPrank();\n\n        // Fee income, then allocation: imdBudget = 30% of the post-reserve remainder (2.85 ETH here).\n        vm.deal(hookStandIn, 10 ether);\n        vm.prank(hookStandIn);\n        (bool ok,) = address(treasury).call{value: 10 ether}(\"\");\n        require(ok, \"income refused\");\n        treasury.allocate();\n        assertGt(treasury.imdBudget(), 0.1 ether);\n    }\n\n    function test_zeroFillIsNotAPurchase() public {\n        assertFalse(treasury.purchased());\n        uint256 budgetBefore = treasury.imdBudget();\n\n        // The operator (operator/operator.py cmd_buy) sends minOut = 0 and relies on the owner's floor. A zero\n        // fill passes that floor, so the contract must refuse on its own: nothing was bought.\n        vm.prank(executor);\n        vm.expectRevert();\n        treasury.buyImd(0.1 ether, 0);\n\n        assertFalse(treasury.purchased(), \"a zero fill must not freeze PRIO and the sinks\");\n        assertEq(treasury.imdBudget(), budgetBefore);\n        assertEq(imd.balanceOf(adapterStandIn), 0);\n    }\n}","reproduction":"State: FeeTreasury configured as in phase A/B of docs/DEPLOYMENT.md (bindHook, setPrio, setSinks, setImd, setImdPool(10000, 200, 0x0), setPriceFloors(1e8 ether, 1e3 ether), setExecutor), imdBudget 2.85 ETH after 10 ETH of fee income and allocate(). ETH/IMD pool initialized at sqrtPrice 2^96 with no IMD-side liquidity. Call: executor -> buyImd(0.1 ether, 0). Expected: revert (nothing bought), purchased() stays false, pool untouched. Actual (measured with the attached test's logs on this tree): returns out = 0, imdBudget unchanged at 2.85 ETH, purchased() == true, ImdBought(0, 0), pool sqrtPrice afterwards 4295128740 (= MIN_SQRT_PRICE + 1); a second buyImd(0.1 ether, 0) reverts with 0x7c9c6e8f (PriceLimitAlreadyExceeded). After this, owner -> setSinks(...) reverts AlreadySet and setPrio(...) reverts AlreadySet although no PRIO or IMD was ever purchased. Run: forge test --match-path test/scratch/BuyImdZeroFill.t.sol (fails now: 'next call did not revert as expected').","severity":"medium","snippet":"        if (out < minOut || out < (spent * floorPerEth) / ONE) revert Slippage();","title":"buyImd accepts a zero-fill swap as a purchase: freezes setPrio/setSinks for ever, parks the IMD pool at MIN_SQRT_PRICE+1, and the operator counts it as filled"},{"citation":"resolved","description":"Boundary: the external call from the Intake into onOracleResult with a fixed 200,000 gas stipend (MockIntake.deliver mirrors it). Measured on this tree with an EOA signer, a uint256 answer and a 40-byte body, after request() so that _forget deletes three slots and all five Result slots plus consumed[requestId] are written cold: 179,593 gas including the CALL overhead. The callback succeeds, so this is not a failing input today, but the margin is about 20,000 gas (10%), not 'well under'. A contract (ERC-1271) signer pinned for a round adds an ecrecover attempt plus a cold staticcall into the signer, and any future SSTORE repricing would push the callback over the stipend; the paid answer is then only recoverable through the executor/owner relay path (submitAttestation), since the Intake's own callback has already failed. Recommend keeping the measured figure in the README instead of 'well under', and keeping an explicit gas test that asserts a margin (e.g. <= 185k).","line":271,"path":"src/OracleAdapter.sol","reproduction":"Deploy OracleAdapter(owner, EOA signer); setIntake/setAction/setPayment(IMD, 0.5e18)/setCallbackConfigured(true)/setExecutor/setBudget(2e18); pinQuestion(1, Q, 1, 5, 4, now - 1h, 40-byte body); fund 10 IMD; executor -> request(1); Intake -> onOracleResult(id, attestation{uint256 answer 7, agreed 5, quorum 4, panelSize 5}, sig) with call{gas: 200000}. Measured gas consumed by the call: 179,593 (forge, solc 0.8.26, optimizer 200 runs, cancun). Expected per the comment: 'well under 200 000'. Actual: 89.8% of the stipend.","severity":"info","snippet":"    /// @notice The Intake's callback for `oracle.request`. Stays well under the 200 000 gas stipend.","title":"Intake callback uses 179,593 of the 200,000 gas stipend on the cold path (10% margin, not 'well under')"}],"hash":"824631bd2926ea8249ef63e162d003aa3538758ec5bc72228b963f14b5072d4b","nodeId":"4010cfa1-add7-4000-8cc9-fc8303e5afb3","outcome":"completed","summary":"The review is complete. `.imd-findings.json` is in the repository root with 2 findings, one embedded proof test, and a coverage row for all 69 listed entry points. No source files were changed.\n\n**Findings**\n\n1. **Medium, `src/FeeTreasury.sol:359`.** A `buyImd` against an ETH/IMD pool with no IMD to sell gets a (0, 0) swap delta and is still accepted as a purchase. The floor check compares `0 < 0 * floor / 1e18`, which is false, and the operator always sends `minOut = 0`. The call then sets `purchased = true`, which permanently freezes `setPrio` and `setSinks` although nothing was bought, emits `ImdBought(0, 0)`, and leaves the pool parked at MIN_SQRT_PRICE + 1 so every later buy reverts with `PriceLimitAlreadyExceeded`. The operator records it as a filled purchase and clears its backoff, the opposite of what the brief asked for. `buyPrio` escapes only because `StakingVault.notifyReward(0)` reverts. Proof: `test/scratch/BuyImdZeroFill.t.sol`, which fails on this tree with \"next call did not revert as expected\". Suggested minimal fix is to refuse an empty fill (`spent == 0 || out == 0`) inside the swap helper.\n\n2. **Info, `src/OracleAdapter.sol:271`.** The Intake callback was measured at 179,593 gas against the 200,000 stipend on the cold path, a 10% margin rather than \"well under\". It passes today. The note recommends a gas assertion in the tests and the measured figure in the docs.\n\n**What held under the math lenses**\n\n- StakingVault: rate and accumulator rounding only strand dust inside the vault. A 2,000-run randomized stake/withdraw/notify/idle fuzz showed no over-claim, no stranded funding beyond dust, and no claim reverts.\n- Arena: `prize / correct` rounds down with the remainder returned to the pool, the reserved escrow always covers the payout, and the winning-choice arithmetic cannot wrap.\n- FeeTreasury `allocate`: the 10% capped reserve then 30/30/40 split sums exactly to the input, with the owner taking rounding dust.\n- Deployment plan: every selector and encoded value in the configuration calldata of the deployment doc matches `cast sig`, and the four constructor arguments match the live records.\n- The existing suite (174 tests) passes.\n\n**Not reached in depth**\n\nThe live TreasuryFeeHook is covered only at the level of its fee rounding, which is dust-level and immutable anyway. The real Intake's callback encoding and request-id convention could not be verified offline.","treeHash":null,"usage":{"cachedInputTokens":2205145,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":60886,"runtime":"claude","turns":40,"wallClockMs":745204}},{"artifacts":[],"attempt":1,"bundleHash":"0deb0032860dc568b00fd588ad0971f8f56fd0c0b30005137187d19102c8d111","device":"0cf632e317dfab7a","findings":[],"hash":"84ba9d490691b8df5b5e728cf886010d324e5b910527eee45005483c807fb018","nodeId":"3bc56393-4779-44b3-9b8a-f07ef52f73af","outcome":"completed","summary":"All four held findings reproduced and are fixed; the advisory ones were each evaluated and handled where real. The full suite passes.\n\n**Held findings, all reproduced by running the proofs on the starting tree (4/4 failed) and now passing (4/4):**\n\n- **Re-pin of an open round via `setArena`**: `setArena` is now one-shot, and `Arena.settle`/`resolved` check that the adapter still pins the question hash the round was created with. The owner's rogue question and self-signed answer are refused; the honest signer's answer still settles.\n- **Result for an uncreated round bricking round ids**: with an Arena configured, neither a relay, the Intake callback nor a paid request is accepted for a round the Arena has not created. A lapsed pin stays replaceable.\n- **Withdrawing IMD by rotating the asset**: every token ever named in `setPayment` is remembered and refused by `withdrawToken`.\n- **Zero fill counted as a purchase**: the treasury swap reverts `NoFill` when the pool spent or returned nothing, so nothing freezes and the pool is not parked at the limit.\n\n**Advisory findings:** the purchase freeze is now per line (PRIO purchase freezes PRIO and its two sinks, IMD purchase freezes the IMD token and the adapter sink, `setImdPool` stays movable and is documented as such). Round length is capped at 30 days. The operator sends `minOut` as 97% of the simulated fill and treats `NoFill` like `PriceLimitAlreadyExceeded`. The deployment record now states the live hook's pending fee ETH and makes the pre-A4 reads a required step. The two info items are recorded as trust assumptions in the README.\n\n**Checks run:**\n\n| Check | Result |\n| --- | --- |\n| `forge build` | ok |\n| `forge test` (in-tree plus the four proofs) | 185 passed, 0 failed |\n| `python3 operator/test_operator.py` | 11 passed |\n| Protected floor test with rebuilt creation codes | 1/1 passed |\n| Mainnet re-read at block 26154735 | pendingEth 390361348266782 wei, treasury 0, owner nonce 163 |\n\nSlither and Mythril were not available and did not run. The test file for the operator now drops its own directory from the module path, because on this machine's Python 3.13 the file name shadowed the standard library's `operator` module and the documented command crashed.\n\nAll 18 finding ids are answered in `.imd-responses.json`, and `ADAPTATION.md` lists each change as rows 11 to 21. No protected path was touched, and `launch.json` was not written.","treeHash":"a4e9e39b8736d3d279bde93c818627096248bc15","usage":{"cachedInputTokens":3696202,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":81090,"runtime":"claude","turns":115,"wallClockMs":1039493}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"04f946173ab09bec","findings":[{"citation":"resolved","description":"Not a defect today and not blocking: the callback still succeeds. Recorded because the revised _accept() now calls _requireCreated(), a cold STATICCALL to IArenaRounds(arena).roundCount() (src/OracleAdapter.sol:327 and :233), on the path the Intake runs with a fixed 200,000 gas stipend. The previous round's measurement (audit_math e89a0757) was 179,593 gas; with a real Arena configured it is now 187,163 gas, so the headroom is about 12,800 gas. An ERC-1271 contract signer pinned for a round, or an SSTORE/CALL repricing, would push the paid callback over the stipend, after which the answer is only recoverable by a relay through submitAttestation (the request stays open since the failed callback reverts _forget). Suggest replacing 'well under' with the measured figure and keeping a regression test that asserts the callback fits with a real Arena and a margin.","line":292,"path":"src/OracleAdapter.sol","reproduction":"Deploy OracleAdapter(owner, EOA signer) and a real Arena(owner, prio); owner: setIntake(intake), setAction, setPayment(IMD, 0.5e18), setCallbackConfigured(true), setExecutor(E), setBudget(2e18), setArena(arena), arena.setOracle(adapter), pinQuestion(1, Q, 1, 5, 4, T, 40-byte body), arena.createRound(VaultRaid, 4, T, T+1h, T+2h, 0, 0, 0); fund 10 IMD; warp(T); E: request(1). In a new block the intake calls onOracleResult(id, attestation{uint256 answer 7, panel 5/4/5, issuedAt T}, sig) with call{gas: 200000}: expected 'well under' the stipend; actual: succeeds using 187,163 gas (forge, solc 0.8.26, optimizer 200, cancun), i.e. 93.6% of the stipend. Scratch test test/scratch/CallbackGas.t.sol run during this review.","severity":"info","snippet":"    /// @notice The Intake's callback for `oracle.request`. Stays well under the 200 000 gas stipend.","title":"OracleAdapter.onOracleResult: the fix for 615b6c64 adds a cold external call inside the Intake's 200,000 gas callback; measured 187,163 gas with a real Arena (6.4% margin), not 'well under'"}],"hash":"d68d38c7d22672c327edf29e5f7e5d397c66a41d54621bfadb72f143678af594","nodeId":"aa562353-5ca8-4ed5-a1a3-24de6c7da0c0","outcome":"completed","summary":"Review complete. The findings file is written and the tree is clean apart from the ignored output file.\n\n**Outcome: every prior finding is settled as fixed, and no new defect reproduced.** The file `.imd-findings.json` holds one info-level note and a coverage row for all 69 entry points plus four invariant rows.\n\n**Blocking findings from last round, all confirmed fixed.** I copied the four attached proofs under test/scratch/ and ran them against the revised code. All four pass, and I traced each fix in source:\n\n- **Open-round re-pin (04dbc34c):** `setArena` now reverts `ArenaAlreadySet` once set, and `Arena.settle` and `resolved` compare the adapter's current pin with the hash frozen at creation, reverting `QuestionChanged` otherwise.\n- **Result for an uncreated round (615b6c64):** `request()` and `_accept()` revert `RoundNotCreated` while the Arena's round count is below the id. The refused attestation is not consumed and the pin stays replaceable.\n- **IMD withdrawable by rotating asset (02cf1770):** `setPayment` records every asset in `wasAsset`, and `withdrawToken` refuses all of them.\n- **Zero fill counted as a purchase (95221d55):** `_swapEthFor` reverts `NoFill` on a zero fill before any state change, so nothing freezes and the pool is not parked at the limit.\n\n**Advisory findings, all confirmed.** The purchase freeze is now per line, with the contract notice and README matching the code. The deployment record states the live hook's pending fee ETH and requires the pre-A4 reads. The operator sends minOut from the simulated quote, and its 11 tests pass. Round length is capped at 30 days. The two trust-assumption items are recorded in the README, and I accepted the author's answer and dropped them.\n\n**One new info note, not a defect.** The fix for the uncreated-round finding adds a cold external call inside the Intake callback, which runs under a fixed stipend. I measured it with a real Arena configured and it still fits:\n\n| Path | Gas used | Stipend |\n| --- | --- | --- |\n| Intake callback, cold, real Arena | 187,163 | 200,000 |\n\nThe NatSpec still says \"well under\", so I recorded the measurement with its reproduction and suggested a regression test with a margin.\n\n**Verification run.** The full reviewed suite passed with 196 tests across 24 suites, zero failures.","treeHash":null,"usage":{"cachedInputTokens":1663058,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":21090,"runtime":"claude","turns":32,"wallClockMs":355195}},{"artifacts":[],"attempt":1,"bundleHash":"d3aea7c652f45bb71e2c7d140551008075fb54ea50a9ee80465beb797b82cf27","device":"896d1238054266ca","findings":[],"hash":"dc9b678f5479758f1fc1895ee2674654dc601f09b790c540696a208140d7fbd6","nodeId":"3bc56393-4779-44b3-9b8a-f07ef52f73af","outcome":"completed","summary":"The work is complete and verified. Final state of the tree is clean, formatted, and all checks pass.\n\n## Summary\n\n**Adapted the PRISM RIOT project for a contracts-only launch** of FeeTreasury, StakingVault, Arena and OracleAdapter on Ethereum mainnet, fixing every reproduced audit finding, with tests.\n\n**Live records verified on mainnet** (block 26154434) before any change: PRIO and the hook exist and are owned by the project owner, the hook's `treasury()` is zero, the original deployment transaction succeeded, the oracle signer is `0x5598aa91…2982` per `api.imd.fun`, and no application contract exists yet (owner wallet history and the launch list both confirm), so the four deployments are not duplicates. No companion address was invented.\n\n**Audit findings handled** (all seven reproduced):\n- Stale `univ4_hook` manifest removed; the manifest step writes the new one from the static arguments table in `docs/DEPLOYMENT.md`.\n- OracleAdapter: a relay from any address settles a round only if it answers the adapter's own request; other attestations need the executor or owner. One open request per round. Pins are correctable until the Arena creates the round.\n- FeeTreasury: sinks are checked against PRIO and bindings stay correctable until first use, then freeze. Executor reserve draws go only to itself within a per-bucket cap. Spend window documented as a fixed bucket with the 2× bound pinned by a test.\n\n**Deliverables added**: `ADAPTATION.md`, `docs/DEPLOYMENT.md` (verification record, manifest arguments, 20 ordered owner transactions with calldata, IMD pool and Intake config, operator budgets, checklist), `script/ConfigPlan.s.sol`, `test/LaunchAdaptation.t.sol`, operator `buy-prio`/`buy-imd` with `PriceLimitAlreadyExceeded` backoff, regenerated ABIs.\n\n**Checks**: `forge build` and `forge test` pass (174 tests), `forge fmt --check` clean, operator tests pass (9), protected floor rehearsal with the four built creation codes passed 1/1. Build configuration and dependencies untouched.\n\n**Not done here, by design**: no deployment, receipts or live application addresses. This repository holds no keys. Those come from the manifest and deployment steps. Preparation of the owner's configuration transactions is not execution. Step A4 (`bindTreasury`) becomes permanent after the first fee, so the owner should verify the FeeTreasury address against the launch record before signing.","treeHash":"ef546d0792d791c61bd638fdabd97096e228acaa","usage":{"cachedInputTokens":4024131,"inputTokens":770,"model":"claude-fable-5-1","outputTokens":91353,"runtime":"claude","turns":57,"wallClockMs":1486501}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0cf632e317dfab7a","findings":[{"citation":"resolved","description":"Round 2 fixed 'a round's result source was not frozen' by having Arena.createRound snapshot the adapter and the question hash and by making a pin immutable once the round exists. Round 3 added setArena/_replaceable so a mistaken pin can be corrected while the configured Arena has not created the round. The guard reads roundCount() from whatever contract `arena` currently points to, and setArena is re-settable at any time. The owner (or a stolen owner key) can therefore point `arena` at any contract whose roundCount() is below the open round's id (a fresh Arena, or a one-line stub), call pinQuestion(roundId, otherQuestion, ...) for a round that is already open with entries, point `arena` back, and then relay an attestation for `otherQuestion` (the owner and the executor are always allowed relayers) which _accept compares only against the current pin. Arena.settle reads resultOf(roundId) and never compares the adapter's pin or the stored Result against the r.questionHash it froze at creation (src/Arena.sol:201 writes it, nothing reads it), so the round settles with the answer to a question the players never bet on. This regresses the documented guarantee (README 'Trust assumptions': an open round's oracle and question are immutable; OracleAdapter NatSpec: 'Once the Arena has created the round the pin is immutable') and lets a privileged actor pick the winning choice of a live round, redirecting prize shares and penalties. Minimal fix that preserves the intended correction window: make setArena one-shot (only while arena == address(0)) or record, per round id, that the pin was consumed the first time the configured Arena's roundCount() reaches it; alternatively have Arena.settle re-read r.oracle.pinned(roundId).questionHash and require equality with r.questionHash (note this alone would leave such a round neither settleable nor cancellable, since resolved() would be true, so the adapter-side fix is preferred).","line":212,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Arena, IRoundOracle} from \"src/Arena.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev Any contract whose roundCount() is lower than an open round's id.\ncontract LowCounter {\n    function roundCount() external pure returns (uint256) {\n        return 0;\n    }\n}\n\n/// @dev Fails on the current tree: after `setArena` is pointed at a contract with a lower `roundCount()`,\n/// the question pinned for a round the real Arena has already created can be rewritten. Passes once the\n/// adapter keeps a pin immutable after the round exists (for example `setArena` only while `arena` is\n/// unset, or a pin marked consumed when the configured Arena's roundCount first reaches it).\ncontract PinReplaceProof is Test {\n    address owner = makeAddr(\"owner\");\n    address signer = makeAddr(\"signer\");\n    PrismRiotToken prio;\n    Arena arena;\n    OracleAdapter adapter;\n    bytes32 constant Q1 = keccak256(\"question for round 1\");\n    bytes32 constant Q2 = keccak256(\"another question\");\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1_800_000_000);\n        prio = new PrismRiotToken();\n        arena = new Arena(owner, address(prio));\n        adapter = new OracleAdapter(owner, signer);\n        vm.startPrank(owner);\n        arena.setOracle(IRoundOracle(address(adapter)));\n        adapter.setArena(address(arena));\n        vm.stopPrank();\n    }\n\n    function test_pinOfACreatedRoundStaysImmutableWhateverArenaIsConfigured() public {\n        uint64 commit = uint64(block.timestamp + 1 hours);\n        vm.startPrank(owner);\n        adapter.pinQuestion(1, Q1, 1, 5, 4, commit, \"body1\");\n        arena.createRound(Arena.Mode.VaultRaid, 3, commit, commit + 1 hours, commit + 2 hours, 0, 0, bytes32(0));\n        assertEq(arena.roundCount(), 1);\n        assertEq(arena.rounds(1).questionHash, Q1);\n        // With the real Arena configured the pin is frozen, as documented.\n        vm.expectRevert(abi.encodeWithSelector(OracleAdapter.AlreadyPinned.selector, 1));\n        adapter.pinQuestion(1, Q2, 1, 5, 4, commit, \"body2\");\n        // Re-point `arena` (ignore whether a fixed adapter refuses this) and try again: it must still be frozen.\n        (bool repointed,) = address(adapter).call(abi.encodeCall(OracleAdapter.setArena, (address(new LowCounter()))));\n        repointed; // a fixed adapter may refuse this; the pin must be frozen either way\n        vm.expectRevert(abi.encodeWithSelector(OracleAdapter.AlreadyPinned.selector, 1));\n        adapter.pinQuestion(1, Q2, 1, 5, 4, commit, \"body2\");\n        vm.stopPrank();\n        assertEq(adapter.pinned(1).questionHash, Q1, \"the open round's question must not change\");\n    }\n}","reproduction":"State: Arena.setOracle(adapter); adapter.setArena(arena); owner pins round 1 with Q1 and notBefore = T; owner createRound(VaultRaid, 3, T, T+1h, T+2h, prize, 0, rules) -> roundCount()==1, rounds(1).questionHash==Q1; players enter. Calls (owner): adapter.pinQuestion(1, Q2, ...) -> reverts AlreadyPinned(1) as documented. adapter.setArena(address(new LowCounter())) where LowCounter.roundCount() returns 0 -> ok. adapter.pinQuestion(1, Q2, 1, 5, 4, T, body2) -> expected AlreadyPinned(1), actual: succeeds, pinned(1).questionHash == Q2 while arena.rounds(1).questionHash == Q1. adapter.setArena(arena) -> ok. At T+1h owner submitAttestation(1, attestation for Q2 signed by the pinned signer, sig) -> stored; arena.settle(1) -> settles with (answerToQ2 % 3) + 1. Proof test fails on this tree with 'next call did not revert as expected'.","severity":"medium","snippet":"        if (arena == address(0) || _results[roundId].settled || openRequest[roundId] != bytes32(0)) return false;\n        return IArenaRounds(arena).roundCount() < roundId;","title":"OracleAdapter: the pinned question of a round the Arena has already created can be rewritten by re-pointing setArena, and the Arena never checks the questionHash it froze"},{"citation":"resolved","description":"The price-floor check scales the floor by `spent`. When the configured ETH/IMD pool has no liquidity below the current price (every 'empty' ETH/IMD pool listed in docs/DEPLOYMENT.md section 4 is such a pool, and a typo in the fee or tick spacing of step B7 selects one), PoolManager.swap walks to the price limit and returns amount0 == 0, amount1 == 0 without reverting. unlockCallback then settles 0 and takes 0, _swapEthFor gets out == 0, spent == 0, and with minImdOut == 0 (which the executor chooses) both halves of the condition are false. buyImd continues: imdBudget is credited back in full, `purchased = true` (src/FeeTreasury.sol:337), safeTransfer(oracleAdapter, 0) succeeds and ImdBought(0, 0) is emitted. From then on setPrio and setSinks revert AlreadySet although no token was ever bought, so a sink or PRIO address that was still meant to be correctable (the round-3 fix 0cd78a87 exists precisely for that) is frozen by an executor call that moved no value. buyPrio is only protected by accident: notifyReward(0) reverts ZeroAmount. Also every later buyImd on that pool reverts PriceLimitAlreadyExceeded because the swap left the pool price at MIN_SQRT_PRICE + 1. Fix: in _swapEthFor revert Slippage() when out == 0 or spent == 0 (a purchase that moves no value is not a purchase), and only set `purchased` after a non-zero out.","line":359,"path":"src/FeeTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {FeeTreasury} from \"src/FeeTreasury.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\nimport {Arena} from \"src/Arena.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\n\n/// @dev Fails on the current tree: `buyImd` against an ETH/IMD pool with no liquidity swaps nothing, sends\n/// nothing, yet sets `purchased` and freezes `setPrio` / `setSinks`. Passes once a zero-output (or\n/// zero-spend) swap is refused.\ncontract ZeroFillPurchaseProof is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    address owner = makeAddr(\"owner\");\n    address executor = makeAddr(\"executor\");\n    address hook = makeAddr(\"hook\"); // stands in for the live hook: the only allowed ETH source\n    address adapter = makeAddr(\"adapter\");\n    PoolManager manager;\n    PrismRiotToken prio;\n    PrismRiotToken imd;\n    FeeTreasury treasury;\n    StakingVault vault;\n    Arena arena;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        prio = new PrismRiotToken();\n        imd = new PrismRiotToken();\n        treasury = new FeeTreasury(IPoolManager(address(manager)), owner);\n        vault = new StakingVault(owner, address(prio));\n        arena = new Arena(owner, address(prio));\n        // An ETH/IMD pool that exists but holds no liquidity (like the empty IMD pools listed in DEPLOYMENT.md §4).\n        PoolKey memory key = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 10_000,\n            tickSpacing: 200,\n            hooks: IHooks(address(0))\n        });\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.startPrank(owner);\n        treasury.bindHook(hook);\n        treasury.setPrio(address(prio));\n        treasury.setSinks(address(vault), address(arena), adapter);\n        treasury.setImd(address(imd));\n        treasury.setImdPool(10_000, 200, address(0));\n        treasury.setPriceFloors(1 ether, 1 ether);\n        treasury.setExecutor(executor);\n        vm.stopPrank();\n        vm.deal(hook, 10 ether);\n        vm.prank(hook);\n        (bool ok,) = address(treasury).call{value: 10 ether}(\"\");\n        require(ok);\n        treasury.allocate();\n    }\n\n    function test_aSwapThatBuysNothingIsNotAPurchase() public {\n        assertFalse(treasury.purchased());\n        vm.prank(executor);\n        (bool ok, bytes memory ret) = address(treasury).call(abi.encodeCall(FeeTreasury.buyImd, (0.1 ether, 0)));\n        if (ok) {\n            (uint256 out) = abi.decode(ret, (uint256));\n            assertEq(out, 0, \"the empty pool gave nothing\");\n            assertEq(imd.balanceOf(adapter), 0, \"nothing was delivered\");\n        }\n        assertFalse(ok, \"a purchase that buys nothing must be refused\");\n        assertFalse(treasury.purchased(), \"no purchase happened, so nothing may be frozen\");\n        vm.prank(owner);\n        treasury.setSinks(address(vault), address(arena), makeAddr(\"correctAdapter\"));\n    }\n}","reproduction":"State: FeeTreasury with hook bound, setPrio(PRIO), setSinks(vault, arena, adapterX), setImd(IMD), setImdPool(10000, 200, 0x0) pointing at an initialized ETH/IMD pool with zero liquidity, setPriceFloors(1e18, 1e18), setExecutor(E); 10 ETH of fee income allocated so imdBudget > 0; purchased() == false. Call (executor E): buyImd(0.1 ether, 0). Expected: revert (nothing can be bought at or above the floor). Actual: returns 0, imdBudget unchanged, IMD balance of the adapter unchanged, purchased() == true; owner's subsequent setSinks(vault, arena, correctAdapter) reverts AlreadySet. Proof test fails on this tree with 'a purchase that buys nothing must be refused'.","severity":"low","snippet":"        if (out < minOut || out < (spent * floorPerEth) / ONE) revert Slippage();","title":"FeeTreasury.buyImd: a swap that buys nothing (empty or drained IMD pool) passes the slippage check, sets `purchased` and permanently freezes setPrio/setSinks without any purchase"},{"citation":"resolved","description":"Read on Ethereum mainnet at block 26154575 (ethereum-rpc.publicnode.com) during this review: TreasuryFeeHook.pendingEth() = 99502487562190 wei, totalFeeCharged() = 99502487562190, totalFeeDelivered() = 0, treasury() = 0, pendingClaims() = 0. A swap has charged a fee since the record at block 26154434. Control-flow consequence for the owner checklist: as soon as A4 (hook.bindTreasury) is mined, anyone can call hook.flush(); it delivers the pending ETH to FeeTreasury.receive(), which sets totalIncome != 0 (bindHook correction closed, src/FeeTreasury.sol:160) and totalFeeDelivered != 0 (bindTreasury correction closed, src/TreasuryFeeHook.sol:181). The document's 'correctable until the first fee arrives' therefore means 'correctable until the first block after A4', possibly the same block. Not a code defect; the checklist should state the current pendingEth and that A1 and A4 must be verified before A4 is signed, since the correction window is effectively zero.","line":13,"path":"docs/DEPLOYMENT.md","reproduction":"cast call 0x65a783cc6725a02ce349dc4d72577994df1760cc 'pendingEth()(uint256)' --rpc-url https://ethereum-rpc.publicnode.com at block 26154575 returns 99502487562190, not 0 as the table states. Locally: after A1-A4 against the live-state replica (pendingEth > 0), a stranger calls hook.flush(); treasury.bindHook(other) then reverts HookAlreadyBound and hook.bindTreasury(other) reverts TreasuryAlreadyBound in the very next transaction.","severity":"info","snippet":"| TreasuryFeeHook | `0x65a783cc6725a02ce349dc4d72577994df1760cc` | `owner()` = `0x13AFB9b5…8EAc`, `pendingOwner()` = 0, `token()` = PRIO, `poolManager()` = `0x0000…8a90`, `factory()` = `0x12C63b58…A96F`, `initialized()` = true, `FEE_BPS()` = 50, `treasury()` = **0**, `pendingEth()` = 0 |","title":"docs/DEPLOYMENT.md section 1 is stale: the live hook already holds pending fee ETH, so the first permissionless flush() after step A4 makes A1 and A4 permanent at once"}],"hash":"e2e11271405b3f35929a8250b7a3f715577cbb8d0e3627a1cf7ca80eef767210","nodeId":"1ef6dbe5-f3ea-4774-b34a-bd6dbee67173","outcome":"completed","summary":"The review is complete. Only the findings file was written; the two proof tests live under the ignored `test/scratch/` directory and nothing else in the tree changed.\n\n**What I checked**\n\n- Read the Senior Auditor's Mindset and my three guides (Execution Trace, Periphery, First Principles), then traced every listed entry point of FeeTreasury, StakingVault, Arena and OracleAdapter, plus the live hook and token where the new contracts touch them.\n- Built and ran the full suite (174 tests pass, including the invariant suites) and verified the live records on mainnet at block 26154575: owner, PRIO symbol, hook state, Intake price 0.5 IMD, and the oracle attester address matching the manifest's signer argument.\n- Audited the manifest adaptation: the four constructor argument lists match parameter order, no constructor calls another contract, and the owner is written as the brief's static address.\n\n**Findings written to `.imd-findings.json`** (3 findings, 73 coverage rows, 2 proofs that fail on the current tree)\n\n1. **Medium.** The pin of an already-created round can be rewritten: `setArena` is re-settable and `_replaceable` trusts whatever contract it points at, while `Arena.settle` never compares its frozen `questionHash`. An owner key can thereby settle a live round with the answer to a different question. Proof fails with \"next call did not revert as expected\".\n2. **Low.** A `buyImd` against an empty ETH/IMD pool swaps nothing yet sets `purchased`, permanently freezing `setPrio` and `setSinks` without any purchase. Proof fails with \"a purchase that buys nothing must be refused\".\n3. **Info.** The deployment doc says the hook's `pendingEth` is zero, but it now holds about 9.95e13 wei, so the first permissionless `flush()` after step A4 closes both binding-correction windows at once.\n\n**Coverage.** Every entry point has a row: 66 hold, 5 are tied to finding 1 or 2, none unreached. The static-analysis leads (reentrancy, strict equality, timestamp) were each traced and found guarded or by design, so they are not reported.","treeHash":null,"usage":{"cachedInputTokens":1849476,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":64371,"runtime":"claude","turns":44,"wallClockMs":913321}},{"artifacts":[],"attempt":1,"bundleHash":"21d7294c6492c7525099b6751f505c91506138efde4f9bcaf4cdeabd944a15ed","device":"5c1e4764d4e20ef5","findings":[],"hash":"f08da0d69e6c96f54b807af2c3945951848d2bd47f8165c23b9945295fc1bef4","nodeId":"803731c0-5e6d-4e31-911d-38cffe857ce7","outcome":"completed","summary":"The revision is complete. Only launch.json changed, and the manifest remains the same four contracts with the same arguments.\n\n**What was verified this round**\n\n- **Build and tests.** The reviewed suite passes with 181 tests across 20 suites, including the regression and invariant tests.\n- **Protected probe.** I rehearsed the pinned protected test in test/scratch against the four creation bytecodes with the manifest's constructor arguments. All four deploy on an empty chain, stay under the EIP-170 limit, and contain no DELEGATECALL, CALLCODE or SELFDESTRUCT.\n- **Oracle signer.** The IMD oracle API still reports the same attester, which signs every attested request as of today. The manifest's OracleAdapter signer matches it.\n- **Live records.** The hook owner is the project owner, the hook's token is PRIO, its PoolManager matches, the fee is 50 basis points, and its treasury is still unbound. The original deployment transaction succeeded.\n- **No duplicates.** The owner's nonce is identical at the launch block, the previous check block and now, so the owner has created no contract since launch and none of the four modules exists yet.\n\n**Manifest contents**\n\n| Contract | Constructor arguments |\n|---|---|\n| FeeTreasury | live PoolManager, project owner |\n| StakingVault | project owner, live PRIO |\n| Arena | project owner, live PRIO |\n| OracleAdapter | project owner, IMD oracle signer |\n\nThe owner is written as the static address the brief names, so nothing is owned by the factory. There is no token, pool, ETH funding or post-deploy callback. The notes stay under the 4000 character limit.\n\n**What changed.** The only edit refreshes the notes' verification evidence to the current block and records that the owner nonce is unchanged since launch. No findings were attached to this revision, so the accepted contracts and arguments were left as they were.\n\n**Next.** The manifest is ready for the independent review and the contracts-only launch. After deployment, the owner signs the configuration transactions in the README's After launch order, starting with binding the hook and PRIO in FeeTreasury before binding the treasury in the live hook.","treeHash":"a235436fa78369121f68aefc5e8aa041f3971b92","usage":{"cachedInputTokens":589143,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":8481,"runtime":"claude","turns":16,"wallClockMs":270177}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d63ea36a2b809080","findings":[{"citation":"resolved","description":"Access x asymmetry (trust gap). `_replaceable` decides whether a pin is still free by asking whatever contract `arena` points at for `roundCount()`, and `setArena` (line 172-176) is an owner setter that can be re-pointed at any time to any address. The Arena stores `r.questionHash` at creation but never compares it, or the signer, against the result it later reads: `settle` (src/Arena.sol:297-300) trusts `r.oracle.resultOf(roundId)` entirely. So the guarantee written at src/OracleAdapter.sol:52 (\"A pin is immutable once the Arena has created its round\"), at :191 (\"Once the Arena has created the round the pin is immutable\") and at src/Arena.sol:40-43 (\"Nothing about an open round can change ... the adapter keeps a pinned question and its signer immutable\") does not hold: after players have committed and revealed, the owner can call setSigner(ownKey), setArena(<any contract whose roundCount() returns 0, or a fresh Arena>), then pinQuestion(roundId, newQuestion, ..., commitDeadline, body) for the OPEN round. The pin is overwritten with the new question hash and the owner's signer; the owner then relays an attestation they signed themselves and `Arena.settle` pays the choice the owner picked. The existing test (test/OracleAdapter.t.sol:343) only checks the stub's roundCount moving forward, never the Arena address moving. Players who entered relying on the published, frozen result source are the victims: the prize (fee-funded PRIO) and the 10 PRIO wrong-choice penalties go to whichever side the owner selects. This also breaks by honest mistake: re-pointing setArena at a redeployed Arena with roundCount 0 while the first Arena still has open rounds on this adapter makes every open round's pin writable. Minimal fix preserving the design: make `setArena` settable once (revert when `arena != address(0)`), or have the adapter record consumption itself (e.g. refuse replacement when the pinned `notBefore` is already <= block.timestamp, and/or have `Arena.createRound` be the only path that marks a pin consumed). Additionally let `Result` carry the question hash and signer so `Arena.settle` can check them against what it stored.","line":213,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {Arena, IRoundOracle} from \"src/Arena.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Reports roundCount() == 0 forever: what the owner points `OracleAdapter.setArena` at.\ncontract EmptyArenaStub {\n    function roundCount() external pure returns (uint256) {\n        return 0;\n    }\n}\n\n/// @dev The adapter promises that a pin is immutable once the Arena has created its round, and the Arena\n/// promises that nothing about an open round's result source can change. `setArena` is re-settable and\n/// `_replaceable` trusts whatever it points at, so the owner can re-pin an OPEN round's question and signer\n/// and settle it with an attestation signed by a key of their choosing.\ncontract RepinOpenRoundTest is Test {\n    uint256 constant SIGNER_KEY = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;\n    address constant SIGNER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;\n    bytes32 constant QUESTION = keccak256(\"round question\");\n    uint64 constant T0 = 1_800_000_000;\n\n    PrismRiotToken token;\n    Arena arena;\n    OracleAdapter adapter;\n    address owner = makeAddr(\"owner\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n    uint256 rogueKey;\n    address rogueSigner;\n\n    function setUp() public {\n        vm.warp(T0);\n        (rogueSigner, rogueKey) = makeAddrAndKey(\"rogue\");\n        token = new PrismRiotToken();\n        arena = new Arena(owner, address(token));\n        adapter = new OracleAdapter(owner, SIGNER);\n        vm.startPrank(owner);\n        arena.setOracle(IRoundOracle(address(adapter)));\n        adapter.setArena(address(arena));\n        vm.stopPrank();\n        token.transfer(alice, 1_000 ether);\n        token.transfer(bob, 1_000 ether);\n        vm.prank(alice);\n        token.approve(address(arena), 102 ether);\n        vm.prank(bob);\n        token.approve(address(arena), 102 ether);\n        token.approve(address(arena), type(uint256).max);\n        arena.fundPrizes(1_000 ether);\n    }\n\n    function attestation(uint256 answer, bytes32 question, uint64 issuedAt)\n        internal\n        view\n        returns (OracleAttestation.Attestation memory a)\n    {\n        a = OracleAttestation.Attestation({\n            requestId: keccak256(abi.encode(\"req\", answer, question)),\n            chainId: 1,\n            questionHash: question,\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(answer),\n            figure: 0,\n            fromBlock: 1,\n            toBlock: 2,\n            blockHash: bytes32(uint256(1)),\n            panelJobId: keccak256(\"job\"),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: issuedAt,\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function sign(uint256 key, OracleAttestation.Attestation memory a) internal view returns (bytes memory) {\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, adapter.attestationDigest(a));\n        return abi.encodePacked(r, s, v);\n    }\n\n    function test_openRoundPinCannotBeReplacedThroughSetArena() public {\n        uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n        vm.startPrank(owner);\n        adapter.pinQuestion(1, QUESTION, 1, 5, 4, commitDeadline, \"\");\n        uint256 id = arena.createRound(\n            Arena.Mode.FactionDuel, 2, commitDeadline, commitDeadline + 1 hours, commitDeadline + 2 hours, 300 ether, 0, 0\n        );\n        vm.stopPrank();\n        assertEq(id, 1);\n\n        // Alice picks 2, Bob picks 1; both reveal.\n        bytes32 saltA = keccak256(\"a\");\n        bytes32 saltB = keccak256(\"b\");\n        bytes32 cA = arena.commitmentOf(id, alice, 2, saltA);\n        bytes32 cB = arena.commitmentOf(id, bob, 1, saltB);\n        vm.prank(alice);\n        arena.enter(id, cA);\n        vm.prank(bob);\n        arena.enter(id, cB);\n        skip(1 hours);\n        vm.prank(alice);\n        arena.reveal(id, 2, saltA);\n        vm.prank(bob);\n        arena.reveal(id, 1, saltB);\n        skip(1 hours);\n\n        // The owner tries to change the open round's result source: rotate the signer to a key they hold,\n        // point the adapter at a contract that reports no rounds, and re-pin round 1.\n        bytes32 rogueQuestion = keccak256(\"rogue question\");\n        vm.startPrank(owner);\n        adapter.setSigner(rogueSigner);\n        (bool arenaSwapped,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.setArena, (address(new EmptyArenaStub()))));\n        (bool repinned,) = address(adapter).call(\n            abi.encodeCall(OracleAdapter.pinQuestion, (id, rogueQuestion, 1, 5, 4, commitDeadline, \"\"))\n        );\n        vm.stopPrank();\n        arenaSwapped; // whether this step is refused or the re-pin is, the pin must survive\n\n        // Expected: the pin the round opened with is untouched.\n        assertFalse(repinned, \"a pin consumed by an open round must not be replaceable\");\n        assertEq(adapter.pinned(id).questionHash, QUESTION, \"question hash changed on an open round\");\n        assertEq(adapter.pinned(id).signer, SIGNER, \"signer changed on an open round\");\n\n        // Expected: an answer signed by the owner's key for the owner's question cannot settle the round.\n        OracleAttestation.Attestation memory rogue = attestation(0, rogueQuestion, commitDeadline); // winning = 1\n        bytes memory rogueSig = sign(rogueKey, rogue);\n        vm.prank(owner);\n        (bool settledByRogue,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.submitAttestation, (id, rogue, rogueSig)));\n        assertFalse(settledByRogue, \"owner-signed answer accepted for an open round\");\n        assertFalse(adapter.resultOf(id).settled);\n\n        // The real signer's answer still settles it (sanity: the honest path is intact).\n        OracleAttestation.Attestation memory real = attestation(1, QUESTION, commitDeadline); // winning = 2\n        bytes memory realSig = sign(SIGNER_KEY, real);\n        vm.prank(owner);\n        adapter.submitAttestation(id, real, realSig);\n        arena.settle(id);\n        assertEq(arena.payoutOf(id, alice), 400 ether);\n        assertEq(arena.payoutOf(id, bob), 90 ether);\n    }\n}","reproduction":"State: Arena.oracle = adapter, adapter.arena = arena (plan step A7), round 1 pinned with QUESTION/signer S, created with commitDeadline T; Alice enters choice 2, Bob choice 1; both reveal; now > revealDeadline. Owner calls: (1) adapter.setSigner(R) where R is a key the owner holds; (2) adapter.setArena(stub) where stub.roundCount() == 0 (any fresh Arena qualifies); (3) adapter.pinQuestion(1, Q2, 1, 5, 4, T, \"\"). Expected: (3) reverts AlreadyPinned(1) because Arena.roundCount() == 1 >= 1. Actual: (3) succeeds; pinned(1).questionHash == Q2 and pinned(1).signer == R. (4) Owner signs an attestation {questionHash: Q2, answer: 0, issuedAt: T, agreed 5/4} with R and calls submitAttestation(1, a, sig): expected BadSignature/QuestionMismatch, actual accepted (resultOf(1).settled == true, answer 0 -> winningChoice 1). (5) arena.settle(1): Bob is paid 100 + prize, Alice gets 90. Test test/scratch/RepinOpenRound.t.sol fails on this tree at the assertion 'a pin consumed by an open round must not be replaceable' and passes once setArena is one-shot (verified against a patched copy).","severity":"medium","snippet":"        return IArenaRounds(arena).roundCount() < roundId;","title":"OracleAdapter: an open round's pinned question and signer can be replaced by re-pointing setArena, defeating the frozen result source"},{"citation":"resolved","description":"Access x economics (trust gap) and an inconsistent-guard asymmetry. `withdrawToken` (line 345-350) promises that \"the IMD bought from fees for agent work cannot be withdrawn: it is spent only on panel answers\", which is what keeps the brief's 30% IMD line from becoming owner income. The check reads `asset` at call time, and `setPayment` (line 153-158) lets the owner replace `asset` with any non-zero address at any moment, with no freeze after the first request or after IMD has arrived. Three owner calls therefore drain the adapter: setPayment(X, 1) ; withdrawToken(IMD, owner, balance) ; setPayment(IMD, 0.5e18). Nothing in the rest of the system notices: FeeTreasury.buyImd keeps forwarding IMD to the adapter (its `oracleAdapter` sink is frozen after the first purchase precisely so the IMD line cannot be redirected), and the economics the brief requires (30% IMD work / 30% PRIO rewards / 40% owner, \"no owner operating advances\") silently become 70% owner. The existing test test/OracleAdapter.t.sol:415 only tries the direct withdrawal. Minimal fix preserving the owner's rescue power: remember every address ever configured as `asset` (or the IMD address once set) and refuse withdrawing any of them; or freeze `asset` after the first request/first IMD receipt while still allowing `price` to change.","line":348,"path":"src/OracleAdapter.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PrismRiotToken} from \"src/PrismRiotToken.sol\";\nimport {OracleAdapter} from \"src/OracleAdapter.sol\";\n\n/// @dev `withdrawToken` refuses only the token that is `asset` at call time, and `setPayment` can move `asset`\n/// at any time. So the IMD bought for agent work from the 30% allocation is withdrawable by the owner in\n/// three calls, contrary to the \"AssetNotWithdrawable\" guarantee and the brief's economics.\ncontract WithdrawImdTest is Test {\n    address owner = makeAddr(\"owner\");\n    address signer = makeAddr(\"signer\");\n    OracleAdapter adapter;\n    PrismRiotToken imd;\n    PrismRiotToken other;\n\n    function setUp() public {\n        adapter = new OracleAdapter(owner, signer);\n        imd = new PrismRiotToken();\n        other = new PrismRiotToken();\n        vm.prank(owner);\n        adapter.setPayment(address(imd), 0.5 ether);\n        // IMD the treasury bought from fees and handed over for panel answers.\n        imd.transfer(address(adapter), 5 ether);\n    }\n\n    function test_configuredImdCannotBeWithdrawnByRotatingTheAsset() public {\n        vm.startPrank(owner);\n        // Direct withdrawal is refused, as documented.\n        vm.expectRevert(OracleAdapter.AssetNotWithdrawable.selector);\n        adapter.withdrawToken(address(imd), owner, 5 ether);\n        // Rotate the asset away, withdraw, rotate it back.\n        (bool rotated,) = address(adapter).call(abi.encodeCall(OracleAdapter.setPayment, (address(other), 1)));\n        (bool withdrawn,) =\n            address(adapter).call(abi.encodeCall(OracleAdapter.withdrawToken, (address(imd), owner, 5 ether)));\n        address(adapter).call(abi.encodeCall(OracleAdapter.setPayment, (address(imd), 0.5 ether)));\n        vm.stopPrank();\n        rotated;\n        // Expected: the IMD bought for agent work stays in the adapter whatever the owner does.\n        assertFalse(withdrawn, \"IMD left the adapter through withdrawToken\");\n        assertEq(imd.balanceOf(address(adapter)), 5 ether, \"IMD for panel answers was withdrawn\");\n        assertEq(imd.balanceOf(owner), 0);\n    }\n}","reproduction":"State: adapter configured per plan step B10 (setPayment(IMD, 0.5e18)); the treasury's buyImd has delivered 5 IMD to the adapter. Owner: withdrawToken(IMD, owner, 5e18) -> reverts AssetNotWithdrawable (as documented). Owner: setPayment(OTHER_TOKEN, 1) -> ok. Owner: withdrawToken(IMD, owner, 5e18) -> expected AssetNotWithdrawable, actual succeeds: imd.balanceOf(adapter) == 0, imd.balanceOf(owner) == 5e18. Owner: setPayment(IMD, 0.5e18) restores the configuration and `paidRequestsEnabled()` is unchanged. Test test/scratch/WithdrawImd.t.sol fails on this tree at 'IMD left the adapter through withdrawToken' and passes against a copy that refuses any token ever configured as asset.","severity":"medium","snippet":"        if (token == asset) revert AssetNotWithdrawable();","title":"OracleAdapter.withdrawToken: the AssetNotWithdrawable guard compares against the mutable `asset`, so the IMD bought from the 30% allocation can be withdrawn by rotating setPayment"},{"citation":"resolved","description":"Asymmetry between paired setters. The notice at lines 55-57 says that once money has flowed \"the 30% PRIO and 30% IMD allocations can never be redirected\", and setPrio (line 168) and setSinks (line 187) enforce `if (purchased) revert AlreadySet()`. setImd (line 175-181) and setImdPool (line 235-246) have no such freeze: after `purchased` is true the owner can still point the IMD line at any token and any PoolKey (fee, tickSpacing and an arbitrary `hooks` address). buyImd (line 326-340) then spends the IMD budget's ETH into that pool and ships whatever `imd` now is to the frozen `oracleAdapter`. Combined with the adapter's withdrawToken behaviour (finding 2) or simply with a pool/hook the owner controls, the 30% IMD line is redirectable after the fact, which is exactly what the freeze on the PRIO side was added to prevent (ADAPTATION.md row 4). The honest-use case also matters: `setImd` to the wrong address after purchases does not revert, while the equivalent mistake on `setPrio` is refused. Owner-only, so low; it is a broken stated guarantee rather than an unprivileged exploit. Fix: apply the same `if (purchased) revert AlreadySet()` guard to setImd and setImdPool (or freeze only `imd` and allow the pool key to be re-pointed to another ETH/IMD pool, which the operator may legitimately need when liquidity moves; state the choice in the notice).","line":175,"path":"src/FeeTreasury.sol","reproduction":"State: treasury bound and seeded as in test/FeeTreasury.t.sol setUp (hook, PRIO, sinks, executor, floors 0.9e18, setImd(PRIO), setImdPool(POOL_FEE, TICK_SPACING, hook)); trader buys 100 ETH of PRIO so fees arrive; allocate(); executor buyPrio(0.1e18, 1) -> purchased == true. Owner: setPrio(PRIO) -> AlreadySet; setSinks(...) -> AlreadySet (as documented). Owner: setImd(0xANY) -> expected AlreadySet, actual succeeds and `imdPoolSet` becomes false; setImdPool(3000, 60, 0xANYHOOK) -> expected AlreadySet, actual succeeds. Reviewer test test/scratch/ImdNotFrozen.t.sol (uses the project Fixture) fails on this tree at 'setImd accepted after the first purchase'.","severity":"low","snippet":"    function setImd(address imd_) external onlyOwner {","title":"FeeTreasury: setImd/setImdPool stay mutable after the first purchase, unlike setPrio/setSinks, so the 30% IMD budget's destination pool and token are not frozen as the contract notice states"},{"citation":"resolved","description":"The verification table (and ADAPTATION.md line 18) records `pendingEth() = 0` for the hook at block 26154434 and §3 describes A4 as \"permanent after the first fee delivery\", which reads as a window that opens only when the next swap happens. On 2026-10-09 at block 26154577 the hook returns pendingEth() = 99502487562190 wei and totalFeeCharged() = 99502487562190 (a swap has already paid a fee that the unbound hook is holding; treasury() is still 0, owner nonce still 163, so no application contract exists yet and the four are still not duplicates). Because TreasuryFeeHook.flush() is permissionless and delivery to any address whose `hook()` is zero or that has no code succeeds, the correction window for A4 is zero in practice: the first delivery can be triggered by anyone in the same block as A4, and the binding then cannot be changed (TreasuryFeeHook.sol:181). The checklist should state this and add a pre-sign read of pendingEth()/pendingClaims(), plus a `cast call` that the address passed to bindTreasury has code and returns hook() == 0x65a783cc...60cc (i.e. A1 confirmed on-chain), before A4 is signed. Documentation/procedure finding; the live hook cannot be changed.","line":13,"path":"docs/DEPLOYMENT.md","reproduction":"Concrete state (mainnet, block 26154577, read with cast on 2026-10-09): 0x65a783cc6725a02ce349dc4d72577994df1760cc.treasury() = 0x0, pendingEth() = 99502487562190, pendingClaims() = 0, totalFeeCharged() = 99502487562190, owner nonce 163. Sequence: owner signs A4 with an address that is not the launched FeeTreasury (typo, or an EOA): bindTreasury accepts it (hook() staticcall to an EOA returns ok with 0 bytes -> no TreasuryMismatch). Any address then calls flush(): 99502487562190 wei is sent to that address, totalFeeDelivered != 0, and bindTreasury reverts TreasuryAlreadyBound for ever; every future 0.5% fee is lost. Expected per the record: \"correctable until the first fee has been delivered\" with pendingEth = 0, i.e. until the next swap. Actual: no correction window exists.","severity":"low","snippet":"| TreasuryFeeHook | `0x65a783cc6725a02ce349dc4d72577994df1760cc` | `owner()` = `0x13AFB9b5…8EAc`, `pendingOwner()` = 0, `token()` = PRIO, `poolManager()` = `0x0000…8a90`, `factory()` = `0x12C63b58…A96F`, `initialized()` = true, `FEE_BPS()` = 50, `treasury()` = **0**, `pendingEth()` = 0 |","title":"Deployment record is stale: the live hook already holds pending fee ETH, so step A4 (bindTreasury) becomes permanent the moment anyone calls flush() after it"},{"citation":"resolved","description":"Access x asymmetry, documented as a trust assumption rather than a defect to fix. A stranger may relay an attestation only when it answers a request this adapter made (`request()` is executor-only). If no paid request is open for a round (executor never called request(), or the request was cleared as stale), only the executor or the owner can put a result on file. Arena.cancel (src/Arena.sol:318-327) is permissionless 72 hours after resultDeadline when no result is on file, so for such a round the privileged side decides the outcome class: relay -> settle (winners paid, losers lose 10 PRIO), withhold -> cancel (everyone refunded, prize back to the pool). The owner may also be a player. The README sells settlement as permissionless; players should know it is conditional on the operator buying the answer on-chain. No code change is required by the brief; if desired, allow any relayer once `block.timestamp >= resultDeadline` for a pinned round (the attestation is still signature-checked against the pinned signer).","line":289,"path":"src/OracleAdapter.sol","reproduction":"State: round 1 open, question pinned, no request() made (paid operations disabled, as the brief says they will be at launch). Alice revealed the correct choice, the owner (as a player) a wrong one. The honest IMD oracle attestation exists off-chain. Stranger calls submitAttestation(1, a, sig): expected acceptance of a correctly signed answer, actual revert NotRelayer (pendingSince[a.requestId] == 0). After resultDeadline + 72h anyone calls cancel(1): succeeds (resolved() false). Payout: owner 102 back instead of 90; Alice 102 instead of 100 + prize share.","severity":"info","snippet":"        } else if (msg.sender != executor && msg.sender != owner()) {","title":"Trust assumption: permissionless settlement of a round depends on the executor having made a paid request; otherwise owner/executor choose between settle and cancel by relaying or withholding the atte"}],"hash":"f81685712bd630cb3b796fb4221e9f64b046145b79be8886ae3687fcd81e9b64","nodeId":"cb40fe97-a234-4384-afe2-17ec2de684d8","outcome":"completed","summary":"The review is complete. The findings file is valid JSON with 5 findings (2 carrying Foundry proofs) and 72 coverage rows, one per listed entry point plus three invariant rows. The working tree is otherwise clean (test/scratch is gitignored, and nothing in src, docs or config was touched).\n\n**What I did**\n\n- Read the Access Control, Trust Gap and Asymmetry guides plus the Senior Auditor's Mindset, then mapped every role, setter and guard across the four application contracts, the live hook, the config plan and the deployment docs.\n- Built the project and ran the reviewed suite: 174 tests pass, including the invariant suites.\n- Verified the live records against mainnet and the IMD API: the hook's treasury is still zero, the owner nonce is unchanged (no application contracts deployed, so the four are not duplicates), the oracle signer matches the API attester, the Intake price is 0.5 IMD, launch 1153 has exactly three artifacts, and the shipped ABIs match the compiled ABIs byte for byte.\n- Wrote three scratch reproductions. All fail on the current tree for the stated reason, and the two medium ones pass against a patched copy of the adapter.\n\n**Findings written to `.imd-findings.json`**\n\n1. **Medium.** An open round's pinned question and signer can be replaced. The adapter decides replaceability by asking whatever address `setArena` points at, and that setter is re-pointable at any time. After players reveal, the owner can rotate the signer, point `arena` at a fresh contract, re-pin the open round and settle it with a self-signed attestation. The Arena never re-checks the question hash or signer it stored. Proof attached.\n2. **Medium.** The IMD bought from the 30% allocation is withdrawable. The adapter's withdrawal guard compares against the mutable `asset`, so rotating `setPayment` away, withdrawing, and rotating back drains it. Proof attached.\n3. **Low.** `setImd` and `setImdPool` are not frozen after the first purchase, while `setPrio` and `setSinks` are. The contract notice promises the IMD line cannot be redirected once money has flowed.\n4. **Low.** The deployment record says the hook's pending fee ETH is zero. It is now non-zero on mainnet, so step A4 becomes permanent the instant anyone calls `flush()` after it. The checklist needs a pre-sign check that the bound address is the launched treasury with the hook already bound.\n5. **Info.** Trust assumption: when no paid request is open, only the owner or executor can relay a result, so they effectively choose between settle and cancel for that round.\n\n**Coverage limits**\n\nFee math inside the live hook's nested swaps, the staking reward arithmetic and the Python operator were outside my area and are marked holds only where no access, trust or asymmetry concern touches them.","treeHash":null,"usage":{"cachedInputTokens":3068530,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":50680,"runtime":"claude","turns":57,"wallClockMs":779068}}],"verification":[{"checks":[{"durationMs":9885,"exitCode":0,"name":"build","output":"Compiling 132 files with Solc 0.8.26\nSolc 0.8.26 finished in 9.70s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:193:15\n    │\n193 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:259:13\n    │\n259 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:13\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:51\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:305:13\n    │\n305 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:310:25\n    │\n310 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:331:13\n    │\n331 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:211:26\n    │\n211 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:291:83\n    │\n291 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:295:9\n    │\n295 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:305:17\n    │\n305 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:315:9\n    │\n315 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:377:31\n    │\n377 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:336:9\n    │\n336 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:338:9\n    │\n338 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:176:26\n    │\n176 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:356:9\n    │\n356 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:372:13\n    │\n372 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:273:13\n    │\n273 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:265:13\n    │\n265 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:278:9\n    │\n278 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:283:44\n    │\n283 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:390:53\n    │\n390 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:24\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:32\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:23\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:31\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:398:9\n    │\n398 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:351:9\n    │\n351 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:482:40\n    │\n482 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/LaunchRehearsal.t.sol:546:40\n    │\n546 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    ‡\n574 │         vm.warp(commitDeadline);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:410:29\n    │\n410 │         a.issuedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:411:30\n    │\n411 │         a.expiresAt = uint64(block.timestamp + 1 hours);\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:424:58\n    │\n424 │         adapter.pinQuestion(2, QUESTION, 1, 5, 4, uint64(block.timestamp + 1 hours), \"\");\n    │                                                          ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:425:54\n    │\n425 │         assertEq(adapter.pinned(2).notBefore, uint64(block.timestamp + 1 hours));\n    │                                                      ━━━━━━━━━━━━━━━\n426 │         stub.set(2);\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:427:17\n    │\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1075,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 591.14µs (224.59µs CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058996)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.20ms (1.81ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28476661)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.25ms (9.41ms CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675614)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783694)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.62ms (8.07ms CPU time)\n\nRan 21 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1405876)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 732316)\n[PASS] test_callbackSelectorIsCanonical() (gas: 463)\n[PASS] test_clearStaleAfterTimeout() (gas: 711223)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 1009082)\n[PASS] test_digestMatchesTheProtocol() (gas: 10881)\n[PASS] test_intakeCallbackForAnUncreatedRoundIsRefused() (gas: 1320141)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 235382)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1228252)\n[PASS] test_noResultForARoundTheArenaHasNotCreated_pinStaysReplaceable() (gas: 1068454)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1070924)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 1005910)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99338)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 904289)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 441520)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 417336)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 714444)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1133267)\n[PASS] test_setArenaIsOneShot() (gas: 514237)\n[PASS] test_withdrawTokenRefusesEveryFormerAsset() (gas: 513269)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 469579)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 72.05ms (10.00ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 0)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 298   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 143   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 161   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 139   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 141   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 142   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 86.84ms (85.40ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 382772, ~: 384896)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393730)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376526)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74578)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343948)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369282)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 107.08ms (78.00ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 360483)\n[PASS] test_budgetWindowBoundary() (gas: 1295353)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 971392)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 1025413)\n[PASS] test_configurationIsOwnerOnly() (gas: 238996)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 752006)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 187151)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 247504)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 340617)\n[PASS] test_ownerCanWithdrawTokens() (gas: 127667)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 443129)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 805141)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 586267)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 429272)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 742511)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 258030)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 127.50ms (8.95ms CPU time)\n\nRan 16 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1163823)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1265528)\n[PASS] test_constantsPublished() (gas: 16246)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361716)\n[PASS] test_entryRules() (gas: 777441)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 985241)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1558965)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1895619)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1253091)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 567960)\n[PASS] test_roundCreationRules() (gas: 132260)\n[PASS] test_roundLengthIsCapped() (gas: 536050)\n[PASS] test_settleRefusesARoundWhosePinnedQuestionChanged() (gas: 1308434)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 828531)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 897556)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1791365)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 146.00ms (7.48ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 656514, ~: 745536)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766255)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410636)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634039)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762979)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381592)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 154.03ms (154.77ms CPU time)\n\nRan 12 tests for test/LaunchRehearsal.t.sol:LaunchRehearsalTest\n[PASS] test_A4RefusesATreasuryBoundElsewhere_andIsCorrectableUntilDelivery() (gas: 8791507)\n[PASS] test_constructorsRefuseZeroArguments() (gas: 6103)\n[PASS] test_executorIsBoundedByThePlanDefaults() (gas: 12892902)\n[PASS] test_fullLifecycleOnTheReplica() (gas: 16934773)\n[PASS] test_manifestDeploysFromTheFactoryWithTheStatedOwner_notTheDeployer() (gas: 8637579)\n[PASS] test_paidOperationsStayOffAfterThePlanUntilFeesFundThem() (gas: 10665000)\n[PASS] test_phaseAOutOfOrder_feesWaitInTheHookAndNothingIsLost() (gas: 9689147)\n[PASS] test_planCalldataAndTargetsMatchTheDeploymentDocument() (gas: 8706969)\n[PASS] test_planIsIdempotentBeforeMoneyMovesAndFrozenAfter() (gas: 12893941)\n[PASS] test_requestBeforeA7IsStrandedUntilStale_thenRecoverable() (gas: 11479913)\n[PASS] test_sinkFreezeIsPerLineOnTheReplica() (gas: 12195266)\n[PASS] test_swappedManifestArgumentsAreCaughtByPhaseA() (gas: 15170975)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 192.98ms (36.57ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 208.73ms (2.94ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562529)\n[PASS] test_buyPrioGuards() (gas: 995064)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035284)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512789)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986377)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43722)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050361)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855954)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273683)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 755033)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1592773)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1870089)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 208.79ms (14.31ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052860)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084368)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980895)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 214.52ms (3.50ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 2039165, ~: 1848370)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 626842, ~: 648921)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661161, ~: 661501)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 238.04ms (590.52ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 11099602)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8698145)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19468506)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 21433927)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 237.98ms (33.41ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380958, ~: 381703)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 369923, ~: 373269)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 370200, ~: 374850)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723633)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126889)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687074)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633616)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 238.17ms (415.33ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 964322, ~: 933938)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3489495, ~: 3163702)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1127966)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1832838)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 499341)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 859783)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 977411)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553779)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1196577)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92706)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 852941)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1421836)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188444)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135488)\n[PASS] test_prizeDustReturnsToPool() (gas: 1318436)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 883265)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187602)\n[PASS] test_zeroCommitmentRefused() (gas: 355459)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 238.26ms (458.22ms CPU time)\n\nRan 20 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606895, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2005031, ~: 1907437)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578041)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917118)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 91010)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 978212)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1407433)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669861)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156232)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823904)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1946009)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2604633)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773453)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88232)\n[PASS] test_zeroFillIsNotAPurchase() (gas: 1671356)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565943)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 238.25ms (367.78ms CPU time)\n\nRan 1 test for test/LaunchRehearsalInvariants.t.sol:LaunchRehearsalInvariantsTest\n[PASS]\nLaunchRehearsalInvariantsTest invariants:\n[PASS] invariant_allocationLinesReconcile\n[PASS] invariant_executorBounds\n[PASS] invariant_incomeConservation\n[PASS] invariant_principalAndEscrowAreIsolated\n[PASS] invariant_purchasesLandWhereTheBriefSays\n[PASS] invariant_treasuryEthIsFullyBucketed\n LaunchRehearsalInvariantsTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭---------------+---------------------------+-------+---------+----------╮\n| Contract      | Selector                  | Calls | Reverts | Discards |\n+========================================================================+\n| SystemHandler | allocate                  | 103   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyExactIn                | 92    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyExactOut               | 98    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyImd                    | 112   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyPrio                   | 99    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | claimRewards              | 92    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | deliverFees               | 100   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | hookIncome                | 99    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | openRound                 | 136   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | requestAnswer             | 115   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | sellExactIn               | 92    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | sellExactOut              | 86    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | stake                     | 109   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | tryWithdrawImd            | 101   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | unstake                   | 91    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | warp                      | 99    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawOwner             | 104   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawReserveAsExecutor | 98    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawReserveAsOwner    | 94    | 0       | 0        |\n╰---------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 542.17ms (533.72ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 6)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 230   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 207   | 6       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 218   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 222   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 213   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 214   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 195   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 213   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 231   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 207   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 215   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 195   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 578.37ms (572.76ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 478   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 487   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 512   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 450   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 494   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 488   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 435   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 496   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 739.82ms (737.57ms CPU time)\n\nRan 1 test for test/OracleAdapterInvariants.t.sol:OracleAdapterInvariantsTest\n[PASS]\nOracleAdapterInvariantsTest invariants:\n[PASS] invariant_budgetWindowHolds\n[PASS] invariant_imdIsSpentOnlyOnAnswers\n[PASS] invariant_openRequestsAreConsistent\n[PASS] invariant_pinsAreFrozenOnceTheirRoundExists\n[PASS] invariant_resultsAreImmutableOnceStored\n[PASS] invariant_resultsOnlyForCreatedRoundsOnceArenaIsBound\n OracleAdapterInvariantsTest invariants (runs: 64, calls: 3200, reverts: 0)\n\n╭----------------+--------------------------+-------+---------+----------╮\n| Contract       | Selector                 | Calls | Reverts | Discards |\n+========================================================================+\n| AdapterHandler | clearStale               | 157   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | createNext               | 178   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | deliver                  | 146   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | fundImd                  | 153   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | ownerRelays              | 154   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | pin                      | 170   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | repin                    | 147   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | replayConsumed           | 163   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | request                  | 322   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | setArena                 | 161   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | setBudget                | 157   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | setPrice                 | 170   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerCannotRequest    | 178   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerRelaysForeign    | 154   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerRelaysOwnRequest | 148   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strayIn                  | 181   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strayOut                 | 153   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | tryWithdrawImd           | 156   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | warp                     | 152   | 0       | 0        |\n╰----------------+--------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 755.41ms (750.81ms CPU time)\n\nRan 1 test for test/StakingVaultInvariants.t.sol:StakingVaultInvariantsTest\n[PASS]\nStakingVaultInvariantsTest invariants:\n[PASS] invariant_accruedNeverExceedsOwed\n[PASS] invariant_balanceIsPrincipalPlusOwedPlusDonations\n[PASS] invariant_principalIsExactPerStaker\n[PASS] invariant_rateCoversRemainingSchedule\n[PASS] invariant_scheduleNeverEndsBeforeTheStreamIsPaid\n StakingVaultInvariantsTest invariants (runs: 96, calls: 4800, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | claim          | 440   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 420   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | exit           | 430   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | fundAsOwner    | 464   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | fundAsTreasury | 442   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | setDuration    | 435   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | stake          | 857   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | strangerNotify | 434   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | warp           | 434   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 444   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 956.15ms (954.19ms CPU time)\n\nRan 24 test suites in 961.94ms (6.31s CPU time): 196 tests passed, 0 failed, 0 skipped (196 total tests)\n","passed":true},{"durationMs":82,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":116,\"LICENSE\":9,\"README.md\":360,\"docs/DEPLOYMENT.md\":161,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1073,\"docs/abi/FeeTreasury.json\":1343,\"docs/abi/OracleAdapter.json\":1559,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"operator/operator.example.json\":26,\"operator/operator.py\":441,\"operator/test_operator.py\":214,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":184,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":369,\"src/FeeTreasury.sol\":407,\"src/OracleAdapter.sol\":374,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":555,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":340,\"test/FeeTreasuryEdge.t.sol\":613,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/LaunchRehearsal.t.sol\":703,\"test/LaunchRehearsalInvariants.t.sol\":482,\"test/OracleAdapter.t.sol\":569,\"test/OracleAdapterEdge.t.sol\":397,\"test/OracleAdapterInvariants.t.sol\":551,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/StakingVaultInvariants.t.sol\":249,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36,\"test/utils/Replica.sol\":281},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0c54c1d3203b610c7a59f2125b537238cd12905efdd9fe528b863733ec799fc3","verifiedTreeHash":"778c64df9d2de0213064f554427e6d564a80bc6a","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":7457,"exitCode":0,"name":"build","output":"Compiling 127 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.29s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:189:15\n    │\n189 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:166:26\n    │\n166 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:254:13\n    │\n254 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:13\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:51\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:296:13\n    │\n296 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:300:25\n    │\n300 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:321:13\n    │\n321 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:199:26\n    │\n199 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:274:83\n    │\n274 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:278:9\n    │\n278 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:252:13\n    │\n252 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:244:13\n    │\n244 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:257:9\n    │\n257 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:262:44\n    │\n262 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:288:17\n    │\n288 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:298:9\n    │\n298 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:356:31\n    │\n356 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:319:9\n    │\n319 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:321:9\n    │\n321 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:322:9\n    │\n322 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:351:13\n    │\n351 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:329:9\n    │\n329 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:368:53\n    │\n368 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:24\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:32\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:23\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:31\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:376:9\n    │\n376 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":866,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 755.05µs (459.31µs CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675592)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783650)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.68ms (1.84ms CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058933)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.93ms (2.89ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052838)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084346)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980851)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 7.88ms (3.17ms CPU time)\n\nRan 14 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1147541)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1249180)\n[PASS] test_constantsPublished() (gas: 16224)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361341)\n[PASS] test_entryRules() (gas: 777404)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 969025)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1542624)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1865160)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1208858)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 565619)\n[PASS] test_roundCreationRules() (gas: 132179)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 812249)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 867276)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1761405)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 8.86ms (9.67ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28066207)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.04ms (10.36ms CPU time)\n\nRan 17 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1374416)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 707902)\n[PASS] test_callbackSelectorIsCanonical() (gas: 485)\n[PASS] test_clearStaleAfterTimeout() (gas: 686787)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 982453)\n[PASS] test_digestMatchesTheProtocol() (gas: 10903)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 233110)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1120241)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1039842)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 979214)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99293)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 877681)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 437066)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 412972)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 694662)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1104180)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 444997)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 69.86ms (9.82ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 2)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 289   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 159   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 151   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 152   | 2       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 123   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 150   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 86.26ms (84.39ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 668581, ~: 745514)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766233)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410614)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634017)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762957)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381570)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 158.40ms (161.27ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 382868, ~: 384852)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393708)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376504)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74556)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343926)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369260)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 166.04ms (177.20ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 10859610)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8483963)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19058834)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 20752128)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 168.30ms (31.90ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 356097)\n[PASS] test_budgetWindowBoundary() (gas: 1261767)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 944763)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 994398)\n[PASS] test_configurationIsOwnerOnly() (gas: 239062)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 725278)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 184958)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 243118)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 336231)\n[PASS] test_ownerCanWithdrawTokens() (gas: 125453)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 416373)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 778401)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 561720)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 404814)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 733739)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 249258)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 168.60ms (10.39ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 950442, ~: 976860)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3327845, ~: 2806608)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1111684)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1744374)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 498716)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 831686)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 961195)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553654)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1180295)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92684)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 834466)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1391443)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188400)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135533)\n[PASS] test_prizeDustReturnsToPool() (gas: 1302286)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 866939)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187624)\n[PASS] test_zeroCommitmentRefused() (gas: 355334)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 187.92ms (354.28ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 187.95ms (2.73ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 1930428, ~: 1721717)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 628115, ~: 648933)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661209, ~: 661510)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 187.96ms (537.84ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380743, ~: 381681)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 372576, ~: 373680)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 372915, ~: 375588)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723589)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126867)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687008)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633550)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 188.02ms (403.81ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562507)\n[PASS] test_buyPrioGuards() (gas: 995005)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035199)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512767)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986380)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43700)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050934)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855869)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273457)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 751231)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1417547)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1869881)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 188.05ms (20.26ms CPU time)\n\nRan 19 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606835, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2019260, ~: 2082678)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578019)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917055)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 90988)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 977969)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1405079)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669993)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156166)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823693)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1945713)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2356765)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773585)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88298)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565921)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 189.84ms (272.38ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 3)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 194   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 225   | 3       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 225   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 215   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 224   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 214   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 244   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 191   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 231   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 179   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 206   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 212   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 570.12ms (566.02ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 468   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 449   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 516   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 483   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 512   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 477   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 468   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 467   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 746.55ms (742.39ms CPU time)\n\nRan 20 test suites in 749.07ms (3.30s CPU time): 174 tests passed, 0 failed, 0 skipped (174 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":88,\"LICENSE\":9,\"README.md\":332,\"docs/DEPLOYMENT.md\":143,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1055,\"docs/abi/FeeTreasury.json\":1312,\"docs/abi/OracleAdapter.json\":1524,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"launch.json\":34,\"operator/operator.example.json\":26,\"operator/operator.py\":396,\"operator/test_operator.py\":177,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":180,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":359,\"src/FeeTreasury.sol\":385,\"src/OracleAdapter.sol\":351,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":466,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":330,\"test/FeeTreasuryEdge.t.sol\":560,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/OracleAdapter.t.sol\":455,\"test/OracleAdapterEdge.t.sol\":397,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5125d7bff4e22e263f7aada80c0570ee74833e2e79f9c1d3eb6267174c8fe9ba","verifiedTreeHash":"f59f6ecebdd1b63a8bafb7aa6dedea5558ab3075","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":9658,"exitCode":0,"name":"build","output":"Compiling 132 files with Solc 0.8.26\nSolc 0.8.26 finished in 9.45s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:166:26\n    │\n166 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:199:26\n    │\n199 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:274:83\n    │\n274 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:278:9\n    │\n278 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:252:13\n    │\n252 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:244:13\n    │\n244 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:257:9\n    │\n257 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:262:44\n    │\n262 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:189:15\n    │\n189 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:288:17\n    │\n288 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:298:9\n    │\n298 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:254:13\n    │\n254 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:13\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:51\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:296:13\n    │\n296 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:300:25\n    │\n300 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:321:13\n    │\n321 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:356:31\n    │\n356 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:319:9\n    │\n319 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:321:9\n    │\n321 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:322:9\n    │\n322 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:351:13\n    │\n351 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:368:53\n    │\n368 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:24\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:32\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:23\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:31\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:376:9\n    │\n376 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:329:9\n    │\n329 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/LaunchRehearsal.t.sol:412:40\n    │\n412 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    ‡\n440 │         vm.warp(commitDeadline);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1114,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 600.07µs (211.35µs CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058933)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.37ms (861.96µs CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675592)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783650)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.32ms (1.83ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052838)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084346)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980851)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 7.02ms (2.48ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28066207)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.15ms (9.01ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 1)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 257   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 166   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 143   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 148   | 1       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 164   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 146   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 91.73ms (89.30ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 381710, ~: 384846)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393708)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376504)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74556)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343926)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369260)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 110.18ms (116.34ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 148.27ms (1.59ms CPU time)\n\nRan 14 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1147541)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1249180)\n[PASS] test_constantsPublished() (gas: 16224)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361341)\n[PASS] test_entryRules() (gas: 777404)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 969025)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1542624)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1865160)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1208858)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 565619)\n[PASS] test_roundCreationRules() (gas: 132179)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 812249)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 867276)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1761405)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 149.75ms (7.92ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 661473, ~: 745514)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766233)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410614)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634017)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762957)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381570)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 152.89ms (153.33ms CPU time)\n\nRan 17 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1374416)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 707902)\n[PASS] test_callbackSelectorIsCanonical() (gas: 485)\n[PASS] test_clearStaleAfterTimeout() (gas: 686787)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 982453)\n[PASS] test_digestMatchesTheProtocol() (gas: 10903)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 233110)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1120241)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1039842)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 979214)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99293)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 877681)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 437066)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 412972)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 694662)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1104180)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 444997)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 178.16ms (15.73ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562507)\n[PASS] test_buyPrioGuards() (gas: 995005)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035199)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512767)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986380)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43700)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050934)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855869)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273457)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 751231)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1417547)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1869881)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 204.85ms (13.18ms CPU time)\n\nRan 19 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606775, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2006917, ~: 2082678)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578019)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917055)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 90988)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 977969)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1405079)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669993)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156166)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823693)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1945713)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2356765)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773585)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88298)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565921)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 204.89ms (372.93ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 10859610)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8483963)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19058834)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 20752128)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 230.52ms (21.38ms CPU time)\n\nRan 10 tests for test/LaunchRehearsal.t.sol:LaunchRehearsalTest\n[PASS] test_A4RefusesATreasuryBoundElsewhere_andIsCorrectableUntilDelivery() (gas: 8577376)\n[PASS] test_constructorsRefuseZeroArguments() (gas: 6103)\n[PASS] test_executorIsBoundedByThePlanDefaults() (gas: 12653373)\n[PASS] test_fullLifecycleOnTheReplica() (gas: 16655330)\n[PASS] test_manifestDeploysFromTheFactoryWithTheStatedOwner_notTheDeployer() (gas: 8423444)\n[PASS] test_paidOperationsStayOffAfterThePlanUntilFeesFundThem() (gas: 10209270)\n[PASS] test_phaseAOutOfOrder_feesWaitInTheHookAndNothingIsLost() (gas: 9474445)\n[PASS] test_planCalldataAndTargetsMatchTheDeploymentDocument() (gas: 8492366)\n[PASS] test_planIsIdempotentBeforeMoneyMovesAndFrozenAfter() (gas: 12764689)\n[PASS] test_swappedManifestArgumentsAreCaughtByPhaseA() (gas: 14817754)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 230.57ms (21.46ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 356097)\n[PASS] test_budgetWindowBoundary() (gas: 1261767)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 944763)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 994398)\n[PASS] test_configurationIsOwnerOnly() (gas: 239062)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 725278)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 184958)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 243118)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 336231)\n[PASS] test_ownerCanWithdrawTokens() (gas: 125453)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 416373)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 778401)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 561720)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 404814)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 733739)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 249258)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 230.59ms (12.76ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 949506, ~: 917771)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3553665, ~: 3150694)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1111684)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1744374)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 498716)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 831686)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 961195)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553654)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1180295)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92684)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 834466)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1391443)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188400)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135533)\n[PASS] test_prizeDustReturnsToPool() (gas: 1302286)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 866939)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187624)\n[PASS] test_zeroCommitmentRefused() (gas: 355334)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 230.71ms (431.93ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 1909675, ~: 1645895)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 624673, ~: 648921)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661164, ~: 661501)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 230.86ms (550.54ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380768, ~: 381681)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 371795, ~: 373680)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 371904, ~: 375355)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723589)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126867)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687008)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633550)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 231.47ms (405.07ms CPU time)\n\nRan 1 test for test/OracleAdapterInvariants.t.sol:OracleAdapterInvariantsTest\n[PASS]\nOracleAdapterInvariantsTest invariants:\n[PASS] invariant_budgetWindowHolds\n[PASS] invariant_imdIsSpentOnlyOnAnswers\n[PASS] invariant_openRequestsAreConsistent\n[PASS] invariant_resultsAreImmutableOnceStored\n OracleAdapterInvariantsTest invariants (runs: 64, calls: 3200, reverts: 354)\n\n╭----------------+--------------------------+-------+---------+----------╮\n| Contract       | Selector                 | Calls | Reverts | Discards |\n+========================================================================+\n| AdapterHandler | clearStale               | 195   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | deliver                  | 176   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | fundImd                  | 199   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | ownerRelays              | 212   | 139     | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | pin                      | 187   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | replayConsumed           | 185   | 23      | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | request                  | 371   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | setBudget                | 177   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | setPrice                 | 172   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerCannotRequest    | 192   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerRelaysForeign    | 192   | 192     | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strangerRelaysOwnRequest | 178   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strayIn                  | 182   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | strayOut                 | 184   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | tryWithdrawImd           | 201   | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| AdapterHandler | warp                     | 197   | 0       | 0        |\n╰----------------+--------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 523.08ms (518.97ms CPU time)\n\nRan 1 test for test/LaunchRehearsalInvariants.t.sol:LaunchRehearsalInvariantsTest\n[PASS]\nLaunchRehearsalInvariantsTest invariants:\n[PASS] invariant_allocationLinesReconcile\n[PASS] invariant_executorBounds\n[PASS] invariant_incomeConservation\n[PASS] invariant_principalAndEscrowAreIsolated\n[PASS] invariant_purchasesLandWhereTheBriefSays\n[PASS] invariant_treasuryEthIsFullyBucketed\n LaunchRehearsalInvariantsTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭---------------+---------------------------+-------+---------+----------╮\n| Contract      | Selector                  | Calls | Reverts | Discards |\n+========================================================================+\n| SystemHandler | allocate                  | 108   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyExactIn                | 123   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyExactOut               | 98    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyImd                    | 114   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | buyPrio                   | 83    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | claimRewards              | 104   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | deliverFees               | 93    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | hookIncome                | 102   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | requestAnswer             | 113   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | sellExactIn               | 109   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | sellExactOut              | 106   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | stake                     | 108   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | tryWithdrawImd            | 125   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | unstake                   | 121   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | warp                      | 114   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawOwner             | 115   | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawReserveAsExecutor | 97    | 0       | 0        |\n|---------------+---------------------------+-------+---------+----------|\n| SystemHandler | withdrawReserveAsOwner    | 87    | 0       | 0        |\n╰---------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 578.29ms (572.47ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 3)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 208   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 184   | 3       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 218   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 218   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 233   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 205   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 219   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 204   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 262   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 211   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 201   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 197   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 649.11ms (641.24ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 494   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 465   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 477   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 480   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 530   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 484   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 428   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 482   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 805.79ms (800.80ms CPU time)\n\nRan 1 test for test/StakingVaultInvariants.t.sol:StakingVaultInvariantsTest\n[PASS]\nStakingVaultInvariantsTest invariants:\n[PASS] invariant_accruedNeverExceedsOwed\n[PASS] invariant_balanceIsPrincipalPlusOwedPlusDonations\n[PASS] invariant_principalIsExactPerStaker\n[PASS] invariant_rateCoversRemainingSchedule\n[PASS] invariant_scheduleNeverEndsBeforeTheStreamIsPaid\n StakingVaultInvariantsTest invariants (runs: 96, calls: 4800, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | claim          | 449   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 432   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | exit           | 433   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | fundAsOwner    | 448   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | fundAsTreasury | 443   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | setDuration    | 407   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | stake          | 895   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | strangerNotify | 415   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | warp           | 435   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 443   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 982.65ms (978.57ms CPU time)\n\nRan 24 test suites in 987.33ms (6.19s CPU time): 187 tests passed, 0 failed, 0 skipped (187 total tests)\n","passed":true},{"durationMs":87,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":88,\"LICENSE\":9,\"README.md\":332,\"docs/DEPLOYMENT.md\":143,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1055,\"docs/abi/FeeTreasury.json\":1312,\"docs/abi/OracleAdapter.json\":1524,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"operator/operator.example.json\":26,\"operator/operator.py\":396,\"operator/test_operator.py\":177,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":180,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":359,\"src/FeeTreasury.sol\":385,\"src/OracleAdapter.sol\":351,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":466,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":330,\"test/FeeTreasuryEdge.t.sol\":560,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/LaunchRehearsal.t.sol\":569,\"test/LaunchRehearsalInvariants.t.sol\":456,\"test/OracleAdapter.t.sol\":455,\"test/OracleAdapterEdge.t.sol\":397,\"test/OracleAdapterInvariants.t.sol\":401,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/StakingVaultInvariants.t.sol\":249,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36,\"test/utils/Replica.sol\":281},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"57c17e145167eb49fdf4124cc1b05959eaa01636c8f25315ea9b59f51518f8f0","verifiedTreeHash":"831f54971a4de790c0cc1ae51a9bd436f83018c4","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":7965,"exitCode":0,"name":"build","output":"Compiling 127 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.79s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:193:15\n    │\n193 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:259:13\n    │\n259 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:176:26\n    │\n176 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:13\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:51\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:305:13\n    │\n305 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:310:25\n    │\n310 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:331:13\n    │\n331 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:211:26\n    │\n211 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:273:13\n    │\n273 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:265:13\n    │\n265 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:278:9\n    │\n278 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:283:44\n    │\n283 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:291:83\n    │\n291 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:295:9\n    │\n295 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:305:17\n    │\n305 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:315:9\n    │\n315 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:377:31\n    │\n377 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:336:9\n    │\n336 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:338:9\n    │\n338 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:356:9\n    │\n356 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:351:9\n    │\n351 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:372:13\n    │\n372 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:390:53\n    │\n390 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:24\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:32\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:23\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:31\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:398:9\n    │\n398 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:482:40\n    │\n482 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:410:29\n    │\n410 │         a.issuedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:411:30\n    │\n411 │         a.expiresAt = uint64(block.timestamp + 1 hours);\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:424:58\n    │\n424 │         adapter.pinQuestion(2, QUESTION, 1, 5, 4, uint64(block.timestamp + 1 hours), \"\");\n    │                                                          ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:425:54\n    │\n425 │         assertEq(adapter.pinned(2).notBefore, uint64(block.timestamp + 1 hours));\n    │                                                      ━━━━━━━━━━━━━━━\n426 │         stub.set(2);\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:427:17\n    │\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":842,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 580.67µs (365.36µs CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675614)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783694)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.42ms (1.12ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052860)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084368)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980895)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.64ms (2.37ms CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058996)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.09ms (1.35ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28476661)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.89ms (9.40ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 0)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 293   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 146   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 136   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 149   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 158   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 142   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 84.89ms (83.51ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 124.28ms (1.87ms CPU time)\n\nRan 21 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1405876)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 732316)\n[PASS] test_callbackSelectorIsCanonical() (gas: 463)\n[PASS] test_clearStaleAfterTimeout() (gas: 711223)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 1009082)\n[PASS] test_digestMatchesTheProtocol() (gas: 10881)\n[PASS] test_intakeCallbackForAnUncreatedRoundIsRefused() (gas: 1320141)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 235382)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1228252)\n[PASS] test_noResultForARoundTheArenaHasNotCreated_pinStaysReplaceable() (gas: 1068454)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1070924)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 1005910)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99338)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 904289)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 441520)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 417336)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 714444)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1133267)\n[PASS] test_setArenaIsOneShot() (gas: 514237)\n[PASS] test_withdrawTokenRefusesEveryFormerAsset() (gas: 513269)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 469579)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 167.29ms (8.24ms CPU time)\n\nRan 20 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606779, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2011238, ~: 1731983)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578041)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917118)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 91010)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 978212)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1407433)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669861)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156232)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823904)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1946009)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2604633)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773453)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88232)\n[PASS] test_zeroFillIsNotAPurchase() (gas: 1671356)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565943)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 178.85ms (351.64ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 11099602)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8698145)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19468506)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 21433927)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 178.84ms (23.08ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 668640, ~: 745536)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766255)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410636)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634039)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762979)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381592)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 183.42ms (185.25ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562529)\n[PASS] test_buyPrioGuards() (gas: 995064)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035284)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512789)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986377)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43722)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050361)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855954)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273683)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 755033)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1592773)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1870089)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 186.14ms (10.78ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380717, ~: 381703)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 372503, ~: 373668)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 371990, ~: 375400)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723633)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126889)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687074)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633616)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 186.26ms (366.65ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 360483)\n[PASS] test_budgetWindowBoundary() (gas: 1295353)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 971392)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 1025413)\n[PASS] test_configurationIsOwnerOnly() (gas: 238996)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 752006)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 187151)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 247504)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 340617)\n[PASS] test_ownerCanWithdrawTokens() (gas: 127667)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 443129)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 805141)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 586267)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 429272)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 742511)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 258030)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 186.38ms (8.17ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 1843256, ~: 1646202)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 627515, ~: 648945)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661194, ~: 661503)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 186.28ms (498.21ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 382556, ~: 384896)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393730)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376526)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74578)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343948)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369282)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 186.36ms (192.93ms CPU time)\n\nRan 16 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1163823)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1265528)\n[PASS] test_constantsPublished() (gas: 16246)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361716)\n[PASS] test_entryRules() (gas: 777441)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 985241)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1558965)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1895619)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1253091)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 567960)\n[PASS] test_roundCreationRules() (gas: 132260)\n[PASS] test_roundLengthIsCapped() (gas: 536050)\n[PASS] test_settleRefusesARoundWhosePinnedQuestionChanged() (gas: 1308434)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 828531)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 897556)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1791365)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 186.34ms (8.65ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 970299, ~: 993100)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3421594, ~: 3165805)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1127966)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1832838)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 499341)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 859783)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 977411)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553779)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1196577)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92706)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 852941)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1421836)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188444)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135488)\n[PASS] test_prizeDustReturnsToPool() (gas: 1318436)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 883265)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187602)\n[PASS] test_zeroCommitmentRefused() (gas: 355459)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 186.36ms (289.59ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 4)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 225   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 208   | 4       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 222   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 187   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 232   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 241   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 224   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 199   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 181   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 223   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 226   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 192   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 528.72ms (523.78ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 471   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 467   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 481   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 511   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 470   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 477   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 481   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 482   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 723.93ms (721.71ms CPU time)\n\nRan 20 test suites in 726.72ms (3.49s CPU time): 181 tests passed, 0 failed, 0 skipped (181 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":116,\"LICENSE\":9,\"README.md\":360,\"docs/DEPLOYMENT.md\":161,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1073,\"docs/abi/FeeTreasury.json\":1343,\"docs/abi/OracleAdapter.json\":1559,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"operator/operator.example.json\":26,\"operator/operator.py\":441,\"operator/test_operator.py\":214,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":184,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":369,\"src/FeeTreasury.sol\":407,\"src/OracleAdapter.sol\":374,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":555,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":340,\"test/FeeTreasuryEdge.t.sol\":613,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/OracleAdapter.t.sol\":569,\"test/OracleAdapterEdge.t.sol\":397,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":4084,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/FeeTreasury.sol:403: FeeTreasury._send(address,uint256) (src/FeeTreasury.sol#403-406) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/TreasuryFeeHook.sol:195: TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[medium/medium] divide-before-multiply at src/StakingVault.sol:138: StakingVault.notifyReward(uint256) (src/StakingVault.sol#138-155) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/OracleAdapter.sol:282: OracleAdapter.clearStale(bytes32) (src/OracleAdapter.sol#282-288) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/OracleAdapter.sol:293: OracleAdapter.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#293-301) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:120: StakingVault.claim() (src/StakingVault.sol#120-128) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:343: Reentrancy in FeeTreasury.buyImd(uint256,uint256) (src/FeeTreasury.sol#343-357):\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:322: Reentrancy in FeeTreasury.buyPrio(uint256,uint256) (src/FeeTreasury.sol#322-340):\n[medium/medium] reentrancy-no-eth at src/OracleAdapter.sol:255: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#255-279):\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:367: Reentrancy in FeeTreasury._swapEthFor(PoolKey,uint256,uint256,uint256) (src/FeeTreasury.sol#367-382):\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:278: TreasuryFeeHook._buyExactInput(uint256,uint160).filled (src/TreasuryFeeHook.sol#278) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:301: TreasuryFeeHook._sellExactOutput(uint256,uint160).prioIn (src/TreasuryFeeHook.sol#301) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:279: TreasuryFeeHook._buyExactInput(uint256,uint160).prioOut (src/TreasuryFeeHook.sol#279) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:300: TreasuryFeeHook._sellExactOutput(uint256,uint160).filled (src/TreasuryFeeHook.sol#300) is a local variable never initialized\n[medium/medium] unused-return at src/FeeTreasury.sol:384: FeeTreasury.unlockCallback(bytes) (src/FeeTreasury.sol#384-401) ignores return value by poolManager.settle{value: owed}() (src/FeeTreasury.sol#398)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:274: TreasuryFeeHook._buyExactInput(uint256,uint160) (src/TreasuryFeeHook.sol#274-293) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/TreasuryFeeHook.sol#277)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:297: TreasuryFeeHook._sellExactOutput(uint256,uint160) (src/TreasuryFeeHook.sol#297-325) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/TreasuryFeeHook.sol#299)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:211: TreasuryFeeHook.redeemClaims(uint256) (src/TreasuryFeeHook.sol#211-216) ignores return value by poolManager.unlock(abi.encode(amount)) (src/TreasuryFeeHook.sol#214)\n[low/medium] missing-zero-check at src/OracleAdapter.sol:176: OracleAdapter.setExecutor(address).to (src/OracleAdapter.sol#176) lacks a zero-check on :\n[low/medium] missing-zero-check at src/StakingVault.sol:66: StakingVault.setRewardFunder(address).funder (src/StakingVault.sol#66) lacks a zero-check on :\n[low/medium] missing-zero-check at src/FeeTreasury.sol:211: FeeTreasury.setExecutor(address).to (src/FeeTreasury.sol#211) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/OracleAdapter.sol:255: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#255-279):\n[low/medium] reentrancy-benign at src/FeeTreasury.sol:343: Reentrancy in FeeTreasury.buyImd(uint256,uint256) (src/FeeTreasury.sol#343-357):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:218: Reentrancy in TreasuryFeeHook.unlockCallback(bytes) (src/TreasuryFeeHook.sol#218-226):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-benign at src/FeeTreasury.sol:322: Reentrancy in FeeTreasury.buyPrio(uint256,uint256) (src/FeeTreasury.sol#322-340):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:274: Reentrancy in TreasuryFeeHook._buyExactInput(uint256,uint160) (src/TreasuryFeeHook.sol#274-293):\n[low/medium] reentrancy-events at src/OracleAdapter.sol:255: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#255-279):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:297: Reentrancy in TreasuryFeeHook._sellExactOutput(uint256,uint160) (src/TreasuryFeeHook.sol#297-325):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:211: Reentrancy in TreasuryFeeHook.redeemClaims(uint256) (src/TreasuryFeeHook.sol#211-216):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:218: Reentrancy in TreasuryFeeHook.unlockCallback(bytes) (src/TreasuryFeeHook.sol#218-226):\n[low/medium] timestamp at src/StakingVault.sol:120: StakingVault.claim() (src/StakingVault.sol#120-128) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:323: OracleAdapter._accept(uint256,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#323-352) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:302: Arena.settle(uint256) (src/Arena.sol#302-324) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:255: OracleAdapter.request(uint256) (src/OracleAdapter.sol#255-279) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:159: StakingVault._update(address) (src/StakingVault.sol#159-176) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:293: OracleAdapter.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#293-301) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:356: OracleAdapter._verifyAttestationBy(address,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#356-365) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:138: StakingVault.notifyReward(uint256) (src/StakingVault.sol#138-155) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:305: OracleAdapter.submitAttestation(uint256,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#305-314) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:91: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#91-99) uses timestamp for comparisons\n[low/medium] timestamp at src/FeeTreasury.sol:301: FeeTreasury.withdrawReserve(address,uint256) (src/FeeTreasury.sol#301-316) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:256: Arena.enter(uint256,bytes32) (src/Arena.sol#256-268) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:270: Arena.reveal(uint256,uint8,bytes32) (src/Arena.sol#270-282) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:179: Arena.createRound(Arena.Mode,uint8,uint64,uint64,uint64,uint256,uint16,bytes32) (src/Arena.sol#179-217) uses timestamp for comparisons\n[low/medium] timestamp at src/FeeTreasury.sol:367: FeeTreasury._swapEthFor(PoolKey,uint256,uint256,uint256) (src/FeeTreasury.sol#367-382) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:328: Arena.cancel(uint256) (src/Arena.sol#328-337) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:282: OracleAdapter.clearStale(bytes32) (src/OracleAdapter.sol#282-288) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:84: StakingVault.rewardPerToken() (src/StakingVault.sol#84-88) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:80: StakingVault.lastTimeRewardApplicable() (src/StakingVault.sol#80-82) uses timestamp for comparisons","passed":true},{"durationMs":677,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/Arena.sol:199: Reentrancy: State change after external call (10 places)\n[high] storage-array-memory-edit at src/FeeTreasury.sol:352: Storage Array Edited with Memory\n[low] centralization-risk at src/Arena.sol:170: Centralization Risk (29 places)\n[low] internal-function-used-once at src/OracleAttestation.sol:117: Internal Function Used Only Once\n[low] large-numeric-literal at src/FeeTreasury.sol:71: Large Numeric Literal (3 places)\n[low] local-variable-shadowing at src/Arena.sol:92: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/FeeTreasury.sol:291: `nonReentrant` is Not the First Modifier\n[low] state-no-address-check at src/FeeTreasury.sol:212: Address State Variable Set Without Checks (3 places)\n[low] unchecked-return at src/TreasuryFeeHook.sol:214: Unchecked Return\n[low] unused-error at src/Arena.sol:139: Unused Error","passed":true},{"durationMs":2675,"exitCode":0,"name":"proof 04dbc34c9ccf","output":"Compiling 109 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.06s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-da944e15/Proof_04dbc34c9ccf.t.sol:RepinOpenRoundTest\n[PASS] test_openRoundPinCannotBeReplacedThroughSetArena() (gas: 1373617)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.65ms (829.03µs CPU time)\n\nRan 1 test suite in 2.97ms (1.65ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1379,"exitCode":0,"name":"proof 615b6c64526e","output":"2026-10-09T12:20:35.305041Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-LoCeys/repo/test/imd-proof-da944e15/Proof_04dbc34c9ccf.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 869.51ms\nCompiler run successful!\n\nRan 1 test for test/imd-proof-aa9a6eb0/Proof_615b6c64526e.t.sol:ProofUncreatedRoundResultBricksArena\n[PASS] test_resultForAnUncreatedRoundMustNotBrickRoundCreation() (gas: 413571)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 987.64µs (308.97µs CPU time)\n\nRan 1 test suite in 3.06ms (987.64µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1200,"exitCode":0,"name":"proof 02cf17703298","output":"2026-10-09T12:20:36.702704Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-LoCeys/repo/test/imd-proof-aa9a6eb0/Proof_615b6c64526e.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 693.39ms\nCompiler run successful with warnings:\nWarning (9302): Return value of low-level calls not used.\n  --> test/imd-proof-36fe7823/Proof_02cf17703298.t.sol:37:9:\n   |\n37 |         address(adapter).call(abi.encodeCall(OracleAdapter.setPayment, (address(imd), 0.5 ether)));\n   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\nRan 1 test for test/imd-proof-36fe7823/Proof_02cf17703298.t.sol:WithdrawImdTest\n[PASS] test_configuredImdCannotBeWithdrawnByRotatingTheAsset() (gas: 184571)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 853.97µs (108.49µs CPU time)\n\nRan 1 test suite in 3.10ms (853.97µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1670,"exitCode":0,"name":"proof 95221d555089","output":"2026-10-09T12:20:37.935492Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-LoCeys/repo/test/imd-proof-36fe7823/Proof_02cf17703298.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.15s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-0f0489ca/Proof_95221d555089.t.sol:BuyImdZeroFillTest\n[PASS] test_zeroFillIsNotAPurchase() (gas: 260238)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.38ms (297.87µs CPU time)\n\nRan 1 test suite in 2.94ms (1.38ms 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":"84ba9d490691b8df5b5e728cf886010d324e5b910527eee45005483c807fb018","verifiedTreeHash":"a4e9e39b8736d3d279bde93c818627096248bc15","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":8102,"exitCode":0,"name":"build","output":"Compiling 127 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.91s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:166:26\n    │\n166 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:189:15\n    │\n189 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:254:13\n    │\n254 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:13\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:268:51\n    │\n268 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:296:13\n    │\n296 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:300:25\n    │\n300 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:321:13\n    │\n321 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:252:13\n    │\n252 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:244:13\n    │\n244 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:257:9\n    │\n257 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:262:44\n    │\n262 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:329:9\n    │\n329 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:199:26\n    │\n199 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:274:83\n    │\n274 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:278:9\n    │\n278 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:288:17\n    │\n288 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:298:9\n    │\n298 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:356:31\n    │\n356 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:319:9\n    │\n319 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:321:9\n    │\n321 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:322:9\n    │\n322 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:351:13\n    │\n351 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:368:53\n    │\n368 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:24\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:372:32\n    │\n372 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:23\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:373:31\n    │\n373 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:376:9\n    │\n376 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:382:22\n    │\n382 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":842,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 561.20µs (256.54µs CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058933)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.76ms (891.17µs CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675592)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783650)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.86ms (2.08ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28066207)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.18ms (8.55ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562507)\n[PASS] test_buyPrioGuards() (gas: 995005)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035199)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512767)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986380)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43700)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050934)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855869)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273457)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 751231)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1417547)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1869881)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 28.02ms (17.77ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 3)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 294   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 147   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 145   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 145   | 3       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 151   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 142   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 79.21ms (77.96ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 10859610)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8483963)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19058834)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 20752128)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 129.08ms (24.68ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380895, ~: 381681)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 370838, ~: 373668)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 371331, ~: 375361)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723589)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126867)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687008)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633550)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 134.54ms (231.17ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052838)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084346)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980851)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 138.07ms (2.79ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 383181, ~: 384867)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393708)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376504)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74556)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343926)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369260)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 138.08ms (110.55ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 660748, ~: 745514)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766233)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410614)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634017)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762957)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381570)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 162.21ms (140.44ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 356097)\n[PASS] test_budgetWindowBoundary() (gas: 1261767)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 944763)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 994398)\n[PASS] test_configurationIsOwnerOnly() (gas: 239062)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 725278)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 184958)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 243118)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 336231)\n[PASS] test_ownerCanWithdrawTokens() (gas: 125453)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 416373)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 778401)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 561720)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 404814)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 733739)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 249258)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 162.30ms (9.12ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 170.23ms (1.56ms CPU time)\n\nRan 17 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1374416)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 707902)\n[PASS] test_callbackSelectorIsCanonical() (gas: 485)\n[PASS] test_clearStaleAfterTimeout() (gas: 686787)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 982453)\n[PASS] test_digestMatchesTheProtocol() (gas: 10903)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 233110)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1120241)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1039842)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 979214)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99293)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 877681)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 437066)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 412972)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 694662)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1104180)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 444997)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 170.27ms (7.34ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 1992366, ~: 1848370)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 629577, ~: 648945)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661198, ~: 661501)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 170.30ms (444.94ms CPU time)\n\nRan 14 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1147541)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1249180)\n[PASS] test_constantsPublished() (gas: 16224)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361341)\n[PASS] test_entryRules() (gas: 777404)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 969025)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1542624)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1865160)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1208858)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 565619)\n[PASS] test_roundCreationRules() (gas: 132179)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 812249)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 867276)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1761405)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 180.28ms (9.98ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 951212, ~: 976872)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3345245, ~: 2805250)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1111684)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1744374)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 498716)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 831686)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 961195)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553654)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1180295)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92684)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 834466)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1391443)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188400)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135533)\n[PASS] test_prizeDustReturnsToPool() (gas: 1302286)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 866939)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187624)\n[PASS] test_zeroCommitmentRefused() (gas: 355334)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 180.31ms (321.37ms CPU time)\n\nRan 19 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606961, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2003450, ~: 1731820)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578019)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917055)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 90988)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 977969)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1405079)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669993)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156166)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823693)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1945713)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2356765)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773585)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88298)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565921)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 180.24ms (351.05ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 2)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 200   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 230   | 2       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 210   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 197   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 183   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 226   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 219   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 218   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 212   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 221   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 213   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 231   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 585.10ms (580.00ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 450   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 482   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 491   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 462   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 501   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 479   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 497   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 478   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 732.67ms (728.37ms CPU time)\n\nRan 20 test suites in 735.34ms (3.36s CPU time): 174 tests passed, 0 failed, 0 skipped (174 total tests)\n","passed":true},{"durationMs":83,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":88,\"LICENSE\":9,\"README.md\":332,\"docs/DEPLOYMENT.md\":143,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1055,\"docs/abi/FeeTreasury.json\":1312,\"docs/abi/OracleAdapter.json\":1524,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"operator/operator.example.json\":26,\"operator/operator.py\":396,\"operator/test_operator.py\":177,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":180,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":359,\"src/FeeTreasury.sol\":385,\"src/OracleAdapter.sol\":351,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":466,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":330,\"test/FeeTreasuryEdge.t.sol\":560,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/OracleAdapter.t.sol\":455,\"test/OracleAdapterEdge.t.sol\":397,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":4413,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/FeeTreasury.sol:381: FeeTreasury._send(address,uint256) (src/FeeTreasury.sol#381-384) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/TreasuryFeeHook.sol:195: TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[medium/medium] divide-before-multiply at src/StakingVault.sol:138: StakingVault.notifyReward(uint256) (src/StakingVault.sol#138-155) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/StakingVault.sol:120: StakingVault.claim() (src/StakingVault.sol#120-128) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/OracleAdapter.sol:261: OracleAdapter.clearStale(bytes32) (src/OracleAdapter.sol#261-267) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/OracleAdapter.sol:272: OracleAdapter.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#272-280) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:305: Reentrancy in FeeTreasury.buyPrio(uint256,uint256) (src/FeeTreasury.sol#305-323):\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:326: Reentrancy in FeeTreasury.buyImd(uint256,uint256) (src/FeeTreasury.sol#326-340):\n[medium/medium] reentrancy-no-eth at src/OracleAdapter.sol:235: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#235-258):\n[medium/medium] reentrancy-no-eth at src/FeeTreasury.sol:346: Reentrancy in FeeTreasury._swapEthFor(PoolKey,uint256,uint256,uint256) (src/FeeTreasury.sol#346-360):\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:278: TreasuryFeeHook._buyExactInput(uint256,uint160).filled (src/TreasuryFeeHook.sol#278) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:301: TreasuryFeeHook._sellExactOutput(uint256,uint160).prioIn (src/TreasuryFeeHook.sol#301) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:279: TreasuryFeeHook._buyExactInput(uint256,uint160).prioOut (src/TreasuryFeeHook.sol#279) is a local variable never initialized\n[medium/medium] uninitialized-local at src/TreasuryFeeHook.sol:300: TreasuryFeeHook._sellExactOutput(uint256,uint160).filled (src/TreasuryFeeHook.sol#300) is a local variable never initialized\n[medium/medium] unused-return at src/FeeTreasury.sol:362: FeeTreasury.unlockCallback(bytes) (src/FeeTreasury.sol#362-379) ignores return value by poolManager.settle{value: owed}() (src/FeeTreasury.sol#376)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:274: TreasuryFeeHook._buyExactInput(uint256,uint160) (src/TreasuryFeeHook.sol#274-293) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/TreasuryFeeHook.sol#277)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:297: TreasuryFeeHook._sellExactOutput(uint256,uint160) (src/TreasuryFeeHook.sol#297-325) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/TreasuryFeeHook.sol#299)\n[medium/medium] unused-return at src/TreasuryFeeHook.sol:211: TreasuryFeeHook.redeemClaims(uint256) (src/TreasuryFeeHook.sol#211-216) ignores return value by poolManager.unlock(abi.encode(amount)) (src/TreasuryFeeHook.sol#214)\n[low/medium] missing-zero-check at src/FeeTreasury.sol:199: FeeTreasury.setExecutor(address).to (src/FeeTreasury.sol#199) lacks a zero-check on :\n[low/medium] missing-zero-check at src/OracleAdapter.sol:166: OracleAdapter.setExecutor(address).to (src/OracleAdapter.sol#166) lacks a zero-check on :\n[low/medium] missing-zero-check at src/StakingVault.sol:66: StakingVault.setRewardFunder(address).funder (src/StakingVault.sol#66) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/FeeTreasury.sol:305: Reentrancy in FeeTreasury.buyPrio(uint256,uint256) (src/FeeTreasury.sol#305-323):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[low/medium] reentrancy-benign at src/OracleAdapter.sol:235: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#235-258):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:218: Reentrancy in TreasuryFeeHook.unlockCallback(bytes) (src/TreasuryFeeHook.sol#218-226):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-benign at src/FeeTreasury.sol:326: Reentrancy in FeeTreasury.buyImd(uint256,uint256) (src/FeeTreasury.sol#326-340):\n[low/medium] reentrancy-benign at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:195: Reentrancy in TreasuryFeeHook.flush() (src/TreasuryFeeHook.sol#195-207):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:274: Reentrancy in TreasuryFeeHook._buyExactInput(uint256,uint160) (src/TreasuryFeeHook.sol#274-293):\n[low/medium] reentrancy-events at src/OracleAdapter.sol:235: Reentrancy in OracleAdapter.request(uint256) (src/OracleAdapter.sol#235-258):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:297: Reentrancy in TreasuryFeeHook._sellExactOutput(uint256,uint160) (src/TreasuryFeeHook.sol#297-325):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:211: Reentrancy in TreasuryFeeHook.redeemClaims(uint256) (src/TreasuryFeeHook.sol#211-216):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:409: Reentrancy in TreasuryFeeHook._collect(uint256,bool,bool,uint256) (src/TreasuryFeeHook.sol#409-421):\n[low/medium] reentrancy-events at src/TreasuryFeeHook.sol:218: Reentrancy in TreasuryFeeHook.unlockCallback(bytes) (src/TreasuryFeeHook.sol#218-226):\n[low/medium] timestamp at src/StakingVault.sol:120: StakingVault.claim() (src/StakingVault.sol#120-128) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:261: OracleAdapter.clearStale(bytes32) (src/OracleAdapter.sol#261-267) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:159: StakingVault._update(address) (src/StakingVault.sol#159-176) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:265: Arena.reveal(uint256,uint8,bytes32) (src/Arena.sol#265-277) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:284: OracleAdapter.submitAttestation(uint256,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#284-293) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:272: OracleAdapter.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#272-280) uses timestamp for comparisons\n[low/medium] timestamp at src/FeeTreasury.sol:284: FeeTreasury.withdrawReserve(address,uint256) (src/FeeTreasury.sol#284-299) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:138: StakingVault.notifyReward(uint256) (src/StakingVault.sol#138-155) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:302: OracleAdapter._accept(uint256,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#302-330) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:91: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#91-99) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:251: Arena.enter(uint256,bytes32) (src/Arena.sol#251-263) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:318: Arena.cancel(uint256) (src/Arena.sol#318-327) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:334: OracleAdapter._verifyAttestationBy(address,OracleAttestation.Attestation,bytes) (src/OracleAdapter.sol#334-343) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAdapter.sol:235: OracleAdapter.request(uint256) (src/OracleAdapter.sol#235-258) uses timestamp for comparisons\n[low/medium] timestamp at src/FeeTreasury.sol:346: FeeTreasury._swapEthFor(PoolKey,uint256,uint256,uint256) (src/FeeTreasury.sol#346-360) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:84: StakingVault.rewardPerToken() (src/StakingVault.sol#84-88) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:293: Arena.settle(uint256) (src/Arena.sol#293-314) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:80: StakingVault.lastTimeRewardApplicable() (src/StakingVault.sol#80-82) uses timestamp for comparisons\n[low/medium] timestamp at src/Arena.sol:175: Arena.createRound(Arena.Mode,uint8,uint64,uint64,uint64,uint256,uint16,bytes32) (src/Arena.sol#175-212) uses timestamp for comparisons","passed":true},{"durationMs":815,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/Arena.sol:194: Reentrancy: State change after external call (9 places)\n[high] storage-array-memory-edit at src/FeeTreasury.sol:335: Storage Array Edited with Memory\n[low] centralization-risk at src/Arena.sol:166: Centralization Risk (29 places)\n[low] internal-function-used-once at src/OracleAttestation.sol:117: Internal Function Used Only Once\n[low] large-numeric-literal at src/FeeTreasury.sol:66: Large Numeric Literal (3 places)\n[low] local-variable-shadowing at src/Arena.sol:92: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/FeeTreasury.sol:274: `nonReentrant` is Not the First Modifier\n[low] state-no-address-check at src/FeeTreasury.sol:200: Address State Variable Set Without Checks (3 places)\n[low] unchecked-return at src/TreasuryFeeHook.sol:214: Unchecked Return\n[low] unused-error at src/Arena.sol:136: Unused Error","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"dc9b678f5479758f1fc1895ee2674654dc601f09b790c540696a208140d7fbd6","verifiedTreeHash":"ef546d0792d791c61bd638fdabd97096e228acaa","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":8059,"exitCode":0,"name":"build","output":"Compiling 127 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.88s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> script/Deploy.s.sol:88:5:\n   |\n88 |     function run() external {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:193:15\n    │\n193 │         if (!(block.timestamp < commitDeadline && commitDeadline < revealDeadline && revealDeadline < resultDeadline)) {\n    │               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:66:30\n   │\n66 │     function setRewardFunder(address funder) external onlyOwner {\n   │                              ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/StakingVault.sol:81:16\n   │\n81 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:259:13\n    │\n259 │         if (block.timestamp >= r.commitDeadline) revert CommitClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:13\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:273:51\n    │\n273 │         if (block.timestamp < r.commitDeadline || block.timestamp >= r.revealDeadline) revert RevealWindowClosed();\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:305:13\n    │\n305 │         if (block.timestamp < r.revealDeadline) revert RevealNotOver();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Arena.sol:310:25\n    │\n310 │         uint8 winning = uint8(res.answer % r.choiceCount) + 1;\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Arena.sol:331:13\n    │\n331 │         if (block.timestamp < uint256(r.resultDeadline) + CANCEL_GRACE) revert NotCancellable();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/OracleAdapter.sol:176:26\n    │\n176 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:144:13\n    │\n144 │         if (block.timestamp < periodFinish) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `openRequest` is updated\n    ╭▸ src/OracleAdapter.sol:273:13\n    │\n273 │             intake.request(action, p.body, IIntake.Callback(address(this), this.onOracleResult.selector), asset, price);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:265:13\n    │\n265 │         if (block.timestamp >= windowStart + BUDGET_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:278:9\n    │\n278 │         emit Requested(roundId, intakeId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OracleAdapter.sol:283:44\n    │\n283 │         if (pendingSince[intakeId] == 0 || block.timestamp < pendingSince[intakeId] + REQUEST_TIMEOUT) {\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `pendingEth` is updated\n    ╭▸ src/TreasuryFeeHook.sol:200:22\n    │\n200 │         (bool ok,) = treasury.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:206:9\n    │\n206 │         emit FeeDelivered(treasury, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/TreasuryFeeHook.sol:214:9\n    │\n214 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:224:9\n    │\n224 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/FeeTreasury.sol:211:26\n    │\n211 │     function setExecutor(address to) external onlyOwner {\n    │                          ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/FeeTreasury.sol:291:83\n    │\n291 │     function withdrawOwner(address payable to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                   ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:295:9\n    │\n295 │         emit OwnerWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OracleAdapter.sol:351:9\n    │\n351 │         emit ResultStored(roundId, answer, a.requestId, a.panelJobId);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:305:17\n    │\n305 │             if (block.timestamp >= reserveWindowStart + SPEND_WINDOW) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:315:9\n    │\n315 │         emit ReserveWithdrawn(to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:267:30\n    │\n267 │             ? _buyExactInput(uint256(-params.amountSpecified), params.sqrtPriceLimitX96)\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:268:32\n    │\n268 │             : _sellExactOutput(uint256(params.amountSpecified), params.sqrtPriceLimitX96);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:292:62\n    │\n292 │         return toBeforeSwapDelta((filled + fee).toInt128(), -prioOut.toInt128());\n    │                                                              ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:289:13\n    │\n289 │         if (filled != leg) fee = feeOnLeg(filled);\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/TreasuryFeeHook.sol:420:9\n    │\n420 │         emit FeeCharged(poolId, zeroForOne, exactInput, leg, fee, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:22\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:286:30\n    │\n286 │             filled = uint256(uint128(-d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:23\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:287:31\n    │\n287 │             prioOut = uint256(uint128(d.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:312:13\n    │\n312 │         if (filled != leg) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/TreasuryFeeHook.sol:324:51\n    │\n324 │         return toBeforeSwapDelta(-pay.toInt128(), prioIn.toInt128());\n    │                                                   ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:22\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:308:30\n    │\n308 │             filled = uint256(uint128(d.amount0()));\n    │                              ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:22\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:309:30\n    │\n309 │             prioIn = uint256(uint128(-d.amount1()));\n    │                              ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `spentInWindow` is updated\n    ╭▸ src/FeeTreasury.sol:377:31\n    │\n377 │         bytes memory result = poolManager.unlock(abi.encode(key, ethIn));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:336:9\n    │\n336 │         IRewardSink(stakingVault).notifyReward(toStaking);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/FeeTreasury.sol:338:9\n    │\n338 │         IPrizeSink(arena).fundPrizes(toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:339:9\n    │\n339 │         emit PrioBought(spent, out, toStaking, toArena);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:38\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:46\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                              ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:68\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:341:76\n    │\n341 │         uint256 leg = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:44\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/TreasuryFeeHook.sol:345:51\n    │\n345 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/FeeTreasury.sol:356:9\n    │\n356 │         emit ImdBought(spent, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/FeeTreasury.sol:372:13\n    │\n372 │         if (block.timestamp >= windowStart + SPEND_WINDOW) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:390:53\n    │\n390 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:24\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:394:32\n    │\n394 │         uint256 owed = uint256(uint128(-delta.amount0()));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:23\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/FeeTreasury.sol:395:31\n    │\n395 │         uint256 out = uint256(uint128(delta.amount1()));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/FeeTreasury.sol:398:9\n    │\n398 │         poolManager.settle{value: owed}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/FeeTreasury.sol:404:22\n    │\n404 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:49:40\n    │\n 49 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Arena.t.sol:482:40\n    │\n482 │         uint64 commitDeadline = uint64(block.timestamp + 1 hours);\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ⸬  lib/forge-std/src/StdCheats.sol:677:9\n    │\n677 │         vm.warp(vm.getBlockTimestamp() + time);\n    │         ────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:488:30\n    │\n488 │         attest(id, 0, uint64(block.timestamp));\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/ArenaEdge.t.sol:88:31\n    │\n 88 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n499 │         vm.warp(uint256(r.resultDeadline) + 72 hours);\n    │         ───────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:410:29\n    │\n410 │         a.issuedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:411:30\n    │\n411 │         a.expiresAt = uint64(block.timestamp + 1 hours);\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:424:58\n    │\n424 │         adapter.pinQuestion(2, QUESTION, 1, 5, 4, uint64(block.timestamp + 1 hours), \"\");\n    │                                                          ━━━━━━━━━━━━━━━\n    ‡\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:425:54\n    │\n425 │         assertEq(adapter.pinned(2).notBefore, uint64(block.timestamp + 1 hours));\n    │                                                      ━━━━━━━━━━━━━━━\n426 │         stub.set(2);\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/OracleAdapter.t.sol:427:17\n    │\n427 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":835,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/PrismRiotToken.t.sol:PrismRiotTokenTest\n[PASS] test_metadataAndSupply() (gas: 44137)\n[PASS] test_noMintOrOwnerFunctions() (gas: 51960)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 808.70µs (398.32µs CPU time)\n\nRan 3 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookClaimsTest\n[PASS] test_buyOnTokenOnlyPoolMintsClaimThenRedeems() (gas: 675614)\n[PASS] test_redeemRefusesMoreThanHeld() (gas: 75537)\n[PASS] test_secondBuyPaysDirectlyOnceManagerHoldsEth() (gas: 783694)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.79ms (1.58ms CPU time)\n\nRan 1 test for test/FeeTreasuryEdge.t.sol:FeeTreasuryThinPoolTest\n[PASS] test_rollingWindowCountsOnlyTheEthActuallySpent() (gas: 1058996)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.28ms (1.55ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployAllPlacesHookOnFlaggedAddress() (gas: 28476661)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.98ms (7.17ms CPU time)\n\nRan 13 tests for test/FeeTreasury.t.sol:FeeTreasuryTest\n[PASS] test_allocationSplitAndReserveCap() (gas: 562529)\n[PASS] test_buyPrioGuards() (gas: 995064)\n[PASS] test_buyPrioSplitsBetweenStakingAndArena() (gas: 1035284)\n[PASS] test_hookBindingIsCorrectableUntilFirstIncome() (gas: 512789)\n[PASS] test_imdPurchaseWaitsForConfiguration_prioIndependent() (gas: 986377)\n[PASS] test_onlyHookCanFund_noOwnerAdvances() (gas: 43722)\n[PASS] test_ownerAndReserveWithdrawalsAreCapped() (gas: 1050361)\n[PASS] test_partialFillCreditsUnspentEthBackToTheBudget() (gas: 1855954)\n[PASS] test_reserveStopsAtTarget() (gas: 878506)\n[PASS] test_rollingWindowAndPriceFloorBoundTheExecutor() (gas: 2273683)\n[PASS] test_setImdUnsetsThePoolKey() (gas: 755033)\n[PASS] test_sinksAreCheckedAndCorrectableUntilFirstPurchase_thenFrozen() (gas: 1592773)\n[PASS] test_spendWindowIsAFixedBucket_atMostTwiceThePerWindowCapInAnyDay() (gas: 1870089)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 11.88ms (9.53ms CPU time)\n\nRan 4 tests for test/LaunchAdaptation.t.sol:LaunchAdaptationTest\n[PASS] test_configurationPlanRunsInOrderAndLeavesTheDocumentedState() (gas: 11099602)\n[PASS] test_constructorsRunOnAnEmptyChainWithStaticArguments() (gas: 8698145)\n[PASS] test_deployScriptMatchesTheManifestArguments() (gas: 19468506)\n[PASS] test_runtimesAreBoundedAndFreeOfEscapeOpcodes() (gas: 21433927)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 32.53ms (31.10ms CPU time)\n\nRan 4 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookUnboundTest\n[PASS] test_feesBeforeBindingWaitThenDeliver() (gas: 1052860)\n[PASS] test_flushRevertsWhenNoTreasuryBound() (gas: 31569)\n[PASS] test_redeemClaimsRevertsWhenManagerHoldsLessEthThanAsked() (gas: 1084368)\n[PASS] test_treasuryRefusingEthNeverRevertsTheSwapAndFeeIsKept() (gas: 980895)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 36.64ms (2.13ms CPU time)\n\nRan 9 tests for test/StakingVault.t.sol:StakingVaultTest\n[PASS] test_durationBounds() (gas: 67910)\n[PASS] test_leftoverRollsIntoNextStream() (gas: 432247)\n[PASS] test_noRewardsWithoutFunding() (gas: 204391)\n[PASS] test_notifyWhileIdleKeepsRemainderAndRestartsSchedule() (gas: 391149)\n[PASS] test_onlyFunderOrOwnerNotifies() (gas: 41455)\n[PASS] test_rewardsProRataByStakeAndTime_andStopAtPeriodEnd() (gas: 643629)\n[PASS] test_stakeWithdrawKeepsPrincipalExact() (gas: 249915)\n[PASS] test_streamPausesAgainWhenEveryoneLeaves() (gas: 675018)\n[PASS] test_streamPausesWhileNothingIsStaked_nothingStranded() (gas: 473558)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 69.66ms (2.68ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:InvariantsTest\n[PASS]\nInvariantsTest invariants:\n[PASS] invariant_arenaBalanceIsFullyAccounted\n[PASS] invariant_vaultCoversPrincipalAndOwedRewards\n InvariantsTest invariants (runs: 32, calls: 1024, reverts: 0)\n\n╭----------+----------+-------+---------+----------╮\n| Contract | Selector | Calls | Reverts | Discards |\n+==================================================+\n| Handler  | claim    | 304   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | enter    | 146   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | fund     | 153   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | stake    | 139   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | warp     | 150   | 0       | 0        |\n|----------+----------+-------+---------+----------|\n| Handler  | withdraw | 132   | 0       | 0        |\n╰----------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 119.21ms (116.39ms CPU time)\n\nRan 12 tests for test/TreasuryFeeHookEdge.t.sol:TreasuryFeeHookEdgeTest\n[PASS] testFuzz_dustBuysChargeOneWei(uint256) (runs: 512, μ: 380982, ~: 381703)\n[PASS] testFuzz_everyShapeSucceedsAcrossMagnitudes(uint256,uint8) (runs: 512, μ: 370899, ~: 373674)\n[PASS] testFuzz_feeIsHalfPercentOfExecutedLeg(uint96,uint8) (runs: 512, μ: 371471, ~: 375347)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 209390)\n[PASS] test_hooklessEthPrioPoolPaysNoHookFee() (gas: 723633)\n[PASS] test_noFeeOrTreasurySetterExists() (gas: 157574)\n[PASS] test_oneWeiBuyIsAllFeeAndDoesNotRevert() (gas: 126889)\n[PASS] test_ownershipIsTwoStepAndNotRenounceable() (gas: 185755)\n[PASS] test_partialFill_afterSwapShapesChargeOnExecutedLegOnly() (gas: 687074)\n[PASS] test_redeemZeroRefused() (gas: 31767)\n[PASS] test_swapOnForeignKeyNamingTheHookIsRefused() (gas: 126722)\n[PASS] test_tinyExactOutputsBothDirections() (gas: 633616)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 158.21ms (284.00ms CPU time)\n\nRan 16 tests for test/Arena.t.sol:ArenaTest\n[PASS] test_bossChallengeNeedsThreshold() (gas: 1163823)\n[PASS] test_cancelAfter72hRefundsEverything() (gas: 1265528)\n[PASS] test_constantsPublished() (gas: 16246)\n[PASS] test_createRoundNeedsPinnedQuestionAtTheCommitBoundary() (gas: 361716)\n[PASS] test_entryRules() (gas: 777441)\n[PASS] test_noWinnerReturnsPrizeToPool() (gas: 985241)\n[PASS] test_oldRoundStaysClaimableAfterNewRounds() (gas: 1558965)\n[PASS] test_ownerCannotSwapOracleForAnOpenRound() (gas: 1895619)\n[PASS] test_resolvedRoundCannotBeCancelled_onlySettled() (gas: 1253091)\n[PASS] test_resultIssuedBeforeCommitBoundaryCannotSettle() (gas: 567960)\n[PASS] test_roundCreationRules() (gas: 132260)\n[PASS] test_roundLengthIsCapped() (gas: 536050)\n[PASS] test_settleRefusesARoundWhosePinnedQuestionChanged() (gas: 1308434)\n[PASS] test_signerRotationDoesNotReachPinnedRounds() (gas: 828531)\n[PASS] test_unresolvedRoundCancelsAndALateResultCannotSettleIt() (gas: 897556)\n[PASS] test_vaultRaidFullLifecycle() (gas: 1791365)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 159.49ms (9.11ms CPU time)\n\nRan 7 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookPartialFillTest\n[PASS] testFuzz_partialFillFeeIsAlwaysOnTheFilledLeg(uint96,bool) (runs: 256, μ: 657343, ~: 745536)\n[PASS] test_buyExactInput_liquidityExhausted_feeIsHalfPercentOfFilledLeg() (gas: 766255)\n[PASS] test_buyExactInput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 410636)\n[PASS] test_fullFillsStillCostExactlyWhatWasSpecified() (gas: 634039)\n[PASS] test_limitNextToSpotMovesNothingAndChargesNothing() (gas: 233336)\n[PASS] test_sellExactOutput_liquidityExhausted_sellerNeverPaysEth() (gas: 762979)\n[PASS] test_sellExactOutput_priceLimit_feeIsHalfPercentOfFilledLeg() (gas: 381592)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 195.53ms (193.08ms CPU time)\n\nRan 13 tests for test/StakingVaultEdge.t.sol:StakingVaultEdgeTest\n[PASS] testFuzz_claimsNeverExceedFunding(uint96,uint8) (runs: 512, μ: 1923247, ~: 1646202)\n[PASS] testFuzz_principalIsExactUnderAnActiveStream(uint96,uint96,uint96,uint32) (runs: 512, μ: 623556, ~: 648921)\n[PASS] testFuzz_proRataByStake(uint96,uint96,uint96) (runs: 512, μ: 661203, ~: 661513)\n[PASS] test_claimWithNothingEarnedIsANoOp() (gas: 237151)\n[PASS] test_constructorRefusesZeroToken() (gas: 6040)\n[PASS] test_exitReturnsPrincipalAndRewards() (gas: 414125)\n[PASS] test_funderWithoutBalanceCannotFund() (gas: 152237)\n[PASS] test_noAccrualBetweenStreams() (gas: 448706)\n[PASS] test_ownershipAndConfigurationGuards() (gas: 282061)\n[PASS] test_proRataByTime() (gas: 517729)\n[PASS] test_strangerCannotFund_andFundingPullsFromFunderOnly() (gas: 522961)\n[PASS] test_withdrawMoreThanStakedRefused_evenWithOthersPrincipalPresent() (gas: 319975)\n[PASS] test_zeroAmountsRefused() (gas: 97272)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 198.56ms (552.78ms CPU time)\n\nRan 16 tests for test/TreasuryFeeHook.t.sol:TreasuryFeeHookTest\n[PASS] testFuzz_feeChargedMatchesTreasuryIncome(uint96,bool,bool) (runs: 256, μ: 382332, ~: 378983)\n[PASS] testFuzz_quotesAreConsistentWeiLevel(uint128) (runs: 256, μ: 22607, ~: 22607)\n[PASS] test_buyExactInput_feeFromEthInput() (gas: 393730)\n[PASS] test_buyExactOutput_feeOnTopOfEthCharged() (gas: 376526)\n[PASS] test_callbacksRefuseNonManager() (gas: 123049)\n[PASS] test_constructorRefusesMismatchedAddress() (gas: 74525)\n[PASS] test_feeOnLegRoundsUpAtWeiLevel() (gas: 22980)\n[PASS] test_initializeOnFreshHookRefusesWrongPair() (gas: 344774)\n[PASS] test_initializeRefusesNonFactoryAndOtherPools() (gas: 48021)\n[PASS] test_oneWeiBuyIsRefusedNotSwallowed() (gas: 439366)\n[PASS] test_otherDirectionsLeaveTokenTransfersUntaxed() (gas: 74578)\n[PASS] test_permissionsAndAddressAgree() (gas: 31553)\n[PASS] test_receiveRefusesEveryoneButThePoolManager() (gas: 38301)\n[PASS] test_sellExactInput_feeOutOfEthOutput() (gas: 343948)\n[PASS] test_sellExactOutput_swapperGetsExactlyTheEthAsked() (gas: 369282)\n[PASS] test_treasuryBindingIsCorrectableUntilFirstDelivery_thenImmutable() (gas: 595165)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 198.49ms (83.71ms CPU time)\n\nRan 17 tests for test/OracleAdapterEdge.t.sol:OracleAdapterEdgeTest\n[PASS] test_boundaryIsInclusiveOnTheChainClock() (gas: 360483)\n[PASS] test_budgetWindowBoundary() (gas: 1295353)\n[PASS] test_callbackAfterManualRelayCannotOverwrite() (gas: 971392)\n[PASS] test_callbackWithBadAttestationLeavesRequestPending() (gas: 1025413)\n[PASS] test_configurationIsOwnerOnly() (gas: 238996)\n[PASS] test_everyConfigurationPieceIsRequired() (gas: 752006)\n[PASS] test_expiryBoundaryIsInclusive() (gas: 187151)\n[PASS] test_issuedInTheFutureBeyondToleranceIsRefused_andWithinToleranceAccepted() (gas: 247504)\n[PASS] test_oneAttestationSettlesOneRoundOnly() (gas: 340617)\n[PASS] test_ownerCanWithdrawTokens() (gas: 127667)\n[PASS] test_pinningRules() (gas: 270918)\n[PASS] test_priceChangeAppliesToNextRequest() (gas: 443129)\n[PASS] test_requestRefusedBeforeBoundarySpendsNothing() (gas: 805141)\n[PASS] test_requestRefusesUnpinnedAndSettledRounds() (gas: 586267)\n[PASS] test_requestWithoutImdBalanceReverts_noIncomeNoOperations() (gas: 429272)\n[PASS] test_signerRotationRevokesOldSigner_andZeroRefused() (gas: 742511)\n[PASS] test_wrongKeyAndMalformedSignaturesRefused() (gas: 258030)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 198.78ms (10.87ms CPU time)\n\nRan 18 tests for test/ArenaEdge.t.sol:ArenaEdgeTest\n[PASS] testFuzz_payoutBounds(uint8,uint8,bool,uint256,uint96) (runs: 512, μ: 966035, ~: 934141)\n[PASS] testFuzz_prizeSplitNeverExceedsPrize(uint8,uint96) (runs: 512, μ: 3389660, ~: 2822590)\n[PASS] test_bossThresholdMetPaysPrize() (gas: 1127966)\n[PASS] test_cancelAndSettleAreExclusiveAfterTheGrace() (gas: 1832838)\n[PASS] test_createRoundRefusesMissingOrMisalignedPin() (gas: 499341)\n[PASS] test_deadlineBoundariesAreHalfOpen() (gas: 859783)\n[PASS] test_entryPullsExactly102AndNothingLater() (gas: 977411)\n[PASS] test_entryWithoutApprovalReverts_andWithShortApprovalReverts() (gas: 553779)\n[PASS] test_factionDuelSplitsPrizeAmongAllCorrect() (gas: 1196577)\n[PASS] test_fundPrizesZeroIsHarmless() (gas: 92706)\n[PASS] test_issuedAtToleranceIsConsistentWithTheAdapter() (gas: 852941)\n[PASS] test_nonEntrantCannotClaimOrRefund() (gas: 1421836)\n[PASS] test_onlyOwnerCreatesAndSetsOracle_andOracleMustBeSetFirst() (gas: 188444)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 135488)\n[PASS] test_prizeDustReturnsToPool() (gas: 1318436)\n[PASS] test_settledRoundCannotBeCancelledAndCancelledCannotSettle() (gas: 883265)\n[PASS] test_unknownRoundRefusesEverything() (gas: 187602)\n[PASS] test_zeroCommitmentRefused() (gas: 355459)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 198.36ms (344.44ms CPU time)\n\nRan 21 tests for test/OracleAdapter.t.sol:OracleAdapterTest\n[PASS] test_budgetAndExecutorEnforced() (gas: 1405876)\n[PASS] test_callbackRefusesWrongSenderAndUnknownId() (gas: 732316)\n[PASS] test_callbackSelectorIsCanonical() (gas: 463)\n[PASS] test_clearStaleAfterTimeout() (gas: 711223)\n[PASS] test_competingAttestationCannotBeRelayedByAStranger_ownRequestCanBeRelayedByAnyone() (gas: 1009082)\n[PASS] test_digestMatchesTheProtocol() (gas: 10881)\n[PASS] test_intakeCallbackForAnUncreatedRoundIsRefused() (gas: 1320141)\n[PASS] test_manualRelayStoresResultAndEvidence() (gas: 235382)\n[PASS] test_mistakenPinCanBeReplacedUntilTheArenaCreatesTheRound() (gas: 1228252)\n[PASS] test_noResultForARoundTheArenaHasNotCreated_pinStaysReplaceable() (gas: 1068454)\n[PASS] test_nothingIsStoredOrRequestedBeforeTheBoundary() (gas: 1070924)\n[PASS] test_ownRequestIdForAnotherRoundIsNotAFreePass() (gas: 1005910)\n[PASS] test_paidRequestDisabledUntilConfigured() (gas: 99338)\n[PASS] test_paidRequestThenCallbackUnder200kGas() (gas: 904289)\n[PASS] test_pinnedSignerOutlivesRotation() (gas: 441520)\n[PASS] test_relayRejectsReplayAndSecondResult() (gas: 417336)\n[PASS] test_relayRejectsWrongQuestionChainQuorumPanelSignerExpiryAndType() (gas: 714444)\n[PASS] test_secondRequestForAnOpenRoundIsRefusedUntilAnsweredOrCleared() (gas: 1133267)\n[PASS] test_setArenaIsOneShot() (gas: 514237)\n[PASS] test_withdrawTokenRefusesEveryFormerAsset() (gas: 513269)\n[PASS] test_withdrawTokenRefusesTheConfiguredAsset() (gas: 469579)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 198.97ms (15.73ms CPU time)\n\nRan 20 tests for test/FeeTreasuryEdge.t.sol:FeeTreasuryEdgeTest\n[PASS] testFuzz_allocationArithmetic(uint96,uint256) (runs: 512, μ: 606788, ~: 606972)\n[PASS] testFuzz_repeatedBuysNeverOverspend(uint8) (runs: 512, μ: 2018789, ~: 1731983)\n[PASS] test_allocateIsIdempotentUntilNewIncome() (gas: 578041)\n[PASS] test_allocateWithNothingIsANoOp() (gas: 76235)\n[PASS] test_buyPrioFailsWithoutFunderRoleAndRollsBack() (gas: 917118)\n[PASS] test_buyPrioOnUnboundTreasuryRefused() (gas: 91010)\n[PASS] test_buyPrioRefusesZeroAndUnsetSinks() (gas: 978212)\n[PASS] test_configurationIsOwnerOnlyAndOneShotWhereStated() (gas: 1407433)\n[PASS] test_executorCannotTakeOwnerBudget_ownerCannotExceedReserve() (gas: 669861)\n[PASS] test_ownershipTwoStepNoRenounce() (gas: 156232)\n[PASS] test_priceFloorBoundaryIsExactOnTheSpentLeg() (gas: 1823904)\n[PASS] test_reserveAboveLoweredTargetGetsNothingMore() (gas: 909135)\n[PASS] test_rollingWindowBoundaries() (gas: 1946009)\n[PASS] test_setImdPoolRequiresImdFirst_thenBuyImdDeliversToAdapter() (gas: 2604633)\n[PASS] test_strangerEthRefusedEvenAfterBinding() (gas: 69919)\n[PASS] test_unlockCallbackOnlyFromManager() (gas: 40214)\n[PASS] test_withdrawToRejectingRecipientRevertsAndKeepsBudget() (gas: 773453)\n[PASS] test_withdrawZeroIsHarmless() (gas: 88232)\n[PASS] test_zeroFillIsNotAPurchase() (gas: 1671356)\n[PASS] test_zeroFloorDisablesPurchasesAgain() (gas: 565943)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 201.84ms (378.27ms CPU time)\n\nRan 1 test for test/TreasuryInvariants.t.sol:TreasuryInvariantsTest\n[PASS]\nTreasuryInvariantsTest invariants:\n[PASS] invariant_hookFeeConservation\n[PASS] invariant_incomeConservation\n[PASS] invariant_managerSolventForClaims\n[PASS] invariant_purchasedPrioIsSplitAndForwarded\n[PASS] invariant_reserveCapped\n[PASS] invariant_treasuryEthIsFullyBucketed\n TreasuryInvariantsTest invariants (runs: 64, calls: 2560, reverts: 1)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | allocate         | 235   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactIn       | 182   | 1       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut      | 216   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | buyPrio          | 214   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 221   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | redeemClaims     | 185   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn      | 220   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut     | 207   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | setReserveTarget | 216   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | warp             | 213   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawOwner    | 206   | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | withdrawReserve  | 245   | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 578.56ms (572.22ms CPU time)\n\nRan 1 test for test/ArenaInvariants.t.sol:ArenaInvariantsTest\n[PASS]\nArenaInvariantsTest invariants:\n[PASS] invariant_balanceFullyAccounted\n[PASS] invariant_playersNeverExtractMoreThanFunded\n[PASS] invariant_roundsAreFrozenOnceFinished\n[PASS] invariant_tokenConservation\n ArenaInvariantsTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭--------------+---------------+-------+---------+----------╮\n| Contract     | Selector      | Calls | Reverts | Discards |\n+===========================================================+\n| ArenaHandler | cancel        | 522   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | claimOrRefund | 469   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | createRound   | 468   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | enter         | 487   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | fund          | 449   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | reveal        | 464   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | settle        | 466   | 0       | 0        |\n|--------------+---------------+-------+---------+----------|\n| ArenaHandler | warp          | 515   | 0       | 0        |\n╰--------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 711.82ms (707.67ms CPU time)\n\nRan 20 test suites in 714.60ms (3.29s CPU time): 181 tests passed, 0 failed, 0 skipped (181 total tests)\n","passed":true},{"durationMs":75,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Arena.acceptOwnership()\",\"Arena.cancel(uint256)\",\"Arena.claim(uint256)\",\"Arena.createRound(uint8,uint8,uint64,uint64,uint64,uint256,uint16,bytes32)\",\"Arena.enter(uint256,bytes32)\",\"Arena.fundPrizes(uint256)\",\"Arena.refund(uint256)\",\"Arena.reveal(uint256,uint8,bytes32)\",\"Arena.setOracle(address)\",\"Arena.settle(uint256)\",\"Arena.transferOwnership(address)\",\"FeeTreasury.acceptOwnership()\",\"FeeTreasury.allocate()\",\"FeeTreasury.bindHook(address)\",\"FeeTreasury.buyImd(uint256,uint256)\",\"FeeTreasury.buyPrio(uint256,uint256)\",\"FeeTreasury.receive()\",\"FeeTreasury.setExecutor(address)\",\"FeeTreasury.setImd(address)\",\"FeeTreasury.setImdPool(uint24,int24,address)\",\"FeeTreasury.setMaxSpendPerSwap(uint256)\",\"FeeTreasury.setPriceFloors(uint256,uint256)\",\"FeeTreasury.setPrio(address)\",\"FeeTreasury.setReservePerWindow(uint256)\",\"FeeTreasury.setReserveTarget(uint256)\",\"FeeTreasury.setSinks(address,address,address)\",\"FeeTreasury.setSpendPerWindow(uint256)\",\"FeeTreasury.transferOwnership(address)\",\"FeeTreasury.unlockCallback(bytes)\",\"FeeTreasury.withdrawOwner(address,uint256)\",\"FeeTreasury.withdrawReserve(address,uint256)\",\"OracleAdapter.acceptOwnership()\",\"OracleAdapter.clearStale(bytes32)\",\"OracleAdapter.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.pinQuestion(uint256,bytes32,uint256,uint16,uint16,uint64,bytes)\",\"OracleAdapter.request(uint256)\",\"OracleAdapter.setAction(bytes32)\",\"OracleAdapter.setArena(address)\",\"OracleAdapter.setBudget(uint256)\",\"OracleAdapter.setCallbackConfigured(bool)\",\"OracleAdapter.setExecutor(address)\",\"OracleAdapter.setIntake(address)\",\"OracleAdapter.setPayment(address,uint256)\",\"OracleAdapter.setSigner(address)\",\"OracleAdapter.submitAttestation(uint256,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"OracleAdapter.transferOwnership(address)\",\"OracleAdapter.withdrawToken(address,address,uint256)\",\"PrismRiotToken.approve(address,uint256)\",\"PrismRiotToken.transfer(address,uint256)\",\"PrismRiotToken.transferFrom(address,address,uint256)\",\"StakingVault.acceptOwnership()\",\"StakingVault.claim()\",\"StakingVault.exit()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.setRewardFunder(address)\",\"StakingVault.setRewardsDuration(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.transferOwnership(address)\",\"StakingVault.withdraw(uint256)\",\"TreasuryFeeHook.acceptOwnership()\",\"TreasuryFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TreasuryFeeHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TreasuryFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TreasuryFeeHook.bindTreasury(address)\",\"TreasuryFeeHook.flush()\",\"TreasuryFeeHook.receive()\",\"TreasuryFeeHook.redeemClaims(uint256)\",\"TreasuryFeeHook.transferOwnership(address)\",\"TreasuryFeeHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":9,\"ADAPTATION.md\":116,\"LICENSE\":9,\"README.md\":360,\"docs/DEPLOYMENT.md\":161,\"docs/OPERATOR.md\":60,\"docs/REVIEW.md\":85,\"docs/abi/Arena.json\":1073,\"docs/abi/FeeTreasury.json\":1343,\"docs/abi/OracleAdapter.json\":1559,\"docs/abi/PrismRiotToken.json\":328,\"docs/abi/StakingVault.json\":627,\"docs/abi/TreasuryFeeHook.json\":1515,\"foundry.toml\":27,\"launch.json\":34,\"operator/operator.example.json\":26,\"operator/operator.py\":441,\"operator/test_operator.py\":214,\"remappings.txt\":6,\"script/ConfigPlan.s.sol\":184,\"script/Deploy.s.sol\":91,\"src/Arena.sol\":369,\"src/FeeTreasury.sol\":407,\"src/OracleAdapter.sol\":374,\"src/OracleAttestation.sol\":125,\"src/PrismRiotToken.sol\":16,\"src/StakingVault.sol\":177,\"src/TreasuryFeeHook.sol\":422,\"src/TwoStepOwned.sol\":19,\"test/Arena.t.sol\":555,\"test/ArenaEdge.t.sol\":516,\"test/ArenaInvariants.t.sol\":289,\"test/Deploy.t.sol\":23,\"test/FeeTreasury.t.sol\":340,\"test/FeeTreasuryEdge.t.sol\":613,\"test/Invariants.t.sol\":155,\"test/LaunchAdaptation.t.sol\":175,\"test/OracleAdapter.t.sol\":569,\"test/OracleAdapterEdge.t.sol\":397,\"test/PrismRiotToken.t.sol\":37,\"test/StakingVault.t.sol\":156,\"test/StakingVaultEdge.t.sol\":268,\"test/TreasuryFeeHook.t.sol\":415,\"test/TreasuryFeeHookEdge.t.sol\":384,\"test/TreasuryInvariants.t.sol\":261,\"test/utils/Fixture.sol\":117,\"test/utils/MockIntake.sol\":36},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f08da0d69e6c96f54b807af2c3945951848d2bd47f8165c23b9945295fc1bef4","verifiedTreeHash":"a235436fa78369121f68aefc5e8aa041f3971b92","verifierVersion":"0.1.0+c4d32abc"}]}