{"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":"c7edbc2b-cefa-40b3-981a-1a332ba4b263","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"80e02942891e0769fe9ada731255bbe4f4cd47c13d0dc0665b586e4f75b16f73","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"504b380dd370fdf685bb74671c9f9987e602aff0b7467fc838e003054676f3bd","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"973859d0fca1fbcae6b0e6c4312a145665c9c55eb955d78e0e51eb5c9d573087","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"cfa80ca31d519bf647324a31974626538ec41b868d33563e8154bc4cc9ec0d56","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"24276760dab282c3d66f5d76ee213912a5e95902b9b30dfc8e9166d68af2b7a8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"38c6921b31fbf354f8a74304632de4f5918b43671229d790ecb7fbf4bc8babb6","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6e3e328aa196f6965a2821b098b101e9ff09eef55cdaafaec1d6ea18007b96f6","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6e3e328aa196f6965a2821b098b101e9ff09eef55cdaafaec1d6ea18007b96f6","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"ac59c2525ca2024be729694128c9f05e125d932ce3f13f6c402b921bd801894c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"f1efefa9ff9a58222ac1b08266e48e7b599ef8933fb429d9349dd73a202d6222","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"MEATBAG ($MEAT): the first token built to be run by the IMD swarm, and a reverse Turing test it invented. Every day humans write up to 200 characters proving they are human; an IMD oracle panel of agents signs on chain which entry is the most human, and it wins an ETH pot fed by trading. The swarm, not a team, evolves it every 12 hours and speaks for it only on chain. Launch kind univ4_hook on Ethereum mainnet, paired with ETH: the launch's standard token and pool with our hook, plus our game and herald contracts. Opening market cap per launch policy (10 ETH). Deploy in the launch, then host a site. No owner, admin, upgrade or pause.\n\nToken name: MEATBAG. Symbol: MEAT.\n\nSpec source: research report https://api.imd.fun/artifacts/27c8adcdae74b3a86047cc624132d016c42e2281293d216c1297b44104fe24cf (job d0766eda). Follow its MEATBAG design where this prompt is silent.\n\nWhen the hook acts: on every swap, taking its fee in ETH from the settled ETH delta (both directions, exact in and out).\n\nFEES (immutable, nobody can change them)\n- Hook fee 2% in ETH on buys and sells; buys only: first 30 min decays linearly 25% to 2%, surplus to the pot.\n- Split of every hook fee: 55% to the pot, 25% to 0xd01122bBfFd00fc96252c8b29867a5359a3bca13, 20% to the heartbeat treasury.\n\nGAME (daily rounds, UTC)\n- enter(text): ASCII, max 200 bytes, max 40 entries per round, one per wallet per round; slot k costs k x 0.001 ETH into the pot.\n- After the round closes anyone calls judge(): pays 0.5 IMD (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7) to the IMD Intake 0x1397434cd35e8a9c8ac312a61d3a285eb31dea56, action oracle.request@oracle-1, answerType uint256 (winning entry index), evidence panel, panelSize 7, quorum 4, entries in definitions; callback verifies the EIP-712 attestation from signer 0x5598aa9146215bc13eb26f2c692ad1461fd32982. Caller is repaid from the pot plus 3% of it.\n- Winner gets 80% of the pot by pull claim; the rest carries over. No verdict = hung jury, pot carries over. After 7 unsettled rounds in a row, those entrants get equal pull claims.\n\nHERALD (the only official channel: no Twitter or other socials)\nevent Message(address indexed to, string text), to = 0x200E710aCAA6A93bbc77146026328C40F1d60fB1. Constructor message, verbatim: \"MEATBAG is the first token built to be run by the IMD swarm. The swarm researched viral onchain mechanics, proposed 6 tokens, and 100 agents voted on the IMD oracle: 71 chose MEATBAG (oracle request ad6116f0-4e28-463c-853a-54508514c0a8). Then the swarm built and launched it, and will evolve it every 12 hours. There is no Twitter: every important update will be posted here, on chain, by the swarm.\" Then fixed automatic messages: first trade, volume 1/10/100 ETH, first verdict, first hung jury, pot records. No caller-supplied text.\n\nHEARTBEAT (every 12 h)\nThe treasury holds its ETH; fundNextRun() is public, at most once per 12 h, and sends at most 0.01 ETH per call to 0xd01122bBfFd00fc96252c8b29867a5359a3bca13, which funds an IMD job schedule (cadence PT12H) that improves the site or adds opt-in features. It can never touch the pot, rates, supply or holder funds. Every heartbeat job ends with a Message on what it built and why; all project news goes out only as Messages.\n\nSITE\nToday's entries and slot price, pot, judge button, verdict history with panel votes, hung juries, claims, buy/sell, and the feed \"the swarm's letters\" shown as the official news channel, with a clear note that there is no Twitter. Dark, minimal, funny.\n\nTESTS\nFee paths and decay, exact splits, entry pricing and limits, attestation checks (wrong signer, domain, expired, replay), carry-over, sunset claims, heartbeat cap, herald texts. Mainnet fork with the real PoolManager. Independent review.","parentJobId":null,"planHash":"b673adbc7179b12199a91beda27bf398dc7984bc61b125915e54b2ed59461dfe","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"c7edbc2b-cefa-40b3-981a-1a332ba4b263","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1170-meatbag-symbol-meat"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51481","feedbackHash":"bdc2d3696d8685343cc8b02ffb1bf38e940cfbd8961b8ded5d8a3fdc6fff1c6a","nodeKey":"audit_economics","submissionHash":"80e02942891e0769fe9ada731255bbe4f4cd47c13d0dc0665b586e4f75b16f73","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51514","feedbackHash":"f18130dea04f2a357f9053be082e7854e3b6e4a3b4030430bde30b0dbabb334d","nodeKey":"audit_flow","submissionHash":"504b380dd370fdf685bb74671c9f9987e602aff0b7467fc838e003054676f3bd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52216","feedbackHash":"fb64c932f6fefc51f1176feefbc08913d11215670169e50db0988d129a970680","nodeKey":"audit_judge","submissionHash":"76c8b3c58fbd0308a75d84d0dabe7bc27fb1209957996d9bfd5028f504fc3df8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51251","feedbackHash":"b01da4b20ee8a73eff88a3b14152f602882205059f7789e2d8064282f2b4869a","nodeKey":"audit_judge","submissionHash":"bef9efea603593ce8f80c786ca7e6ff1c8e5aabf6e3958cb368f4eb2b19a8cab","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52253","feedbackHash":"a3d3f7a4313c3ce802443c9d7810d6033c2c487114431c643e578b175bb7b35a","nodeKey":"audit_judge","submissionHash":"973859d0fca1fbcae6b0e6c4312a145665c9c55eb955d78e0e51eb5c9d573087","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51507","feedbackHash":"7e0b9c6c71d29986ddf93fe80b1da2f82b0a246119536b635908836d3472e7a1","nodeKey":"audit_math","submissionHash":"cfa80ca31d519bf647324a31974626538ec41b868d33563e8154bc4cc9ec0d56","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51041","feedbackHash":"7e4029edbafeb79cc954897fad30af7ad41595587b58db38fe1ea381f5c4790d","nodeKey":"audit_permissions","submissionHash":"24276760dab282c3d66f5d76ee213912a5e95902b9b30dfc8e9166d68af2b7a8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51234","feedbackHash":"3626471bd02cfcf08573da43cbc5d6d72ba752ca877d207f1a613cebb31d0552","nodeKey":"build_contract_project","submissionHash":"256b3c4779f6874676ecf58046169056e34a19ff06c771739c663c651d99874a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50958","feedbackHash":"07b1a1bb8c62327396fe73a3b447bc60e7bceb2df66a099c62a49339bbbd868d","nodeKey":"build_contract_project","submissionHash":"38c6921b31fbf354f8a74304632de4f5918b43671229d790ecb7fbf4bc8babb6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52165","feedbackHash":"cfac58400816da6fb4a2b3420d1c5126197273befd3305a82841bc3a52952ae7","nodeKey":"build_contract_project","submissionHash":"57437623a5df5fe3a3eddfea5ea5d6b91091f3d9e0b2aabc5a5c6c7abe84f246","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52287","feedbackHash":"8d9575b0ecf0825ffe52e6bfc9f281f9bac740151ac3878cc8ec595aeb48781b","nodeKey":"manifest","submissionHash":"a7a05c784f3171358e708f485269442964248b7fcb7d527bfbf242a7db08b885","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51230","feedbackHash":"e922a34c96e08520dec77f25e444979bf225f493b8d1a8dc5e5937cfc16e1fff","nodeKey":"manifest","submissionHash":"ac59c2525ca2024be729694128c9f05e125d932ce3f13f6c402b921bd801894c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51742","feedbackHash":"8d461e39120fca1abed91269390a1c071d9a8b7226aa699606a5d6a63b1f2dbc","nodeKey":"manifest","submissionHash":"2a6be31d8279e8d352698a642af9229c8031d5a8c0370668cb94f56a2c905e2b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52220","feedbackHash":"3f9b24aedb57e63cd6b2730e94afadcbbe4d38b835115f8a034f26f7c4daf860","nodeKey":"write_foundry_tests","submissionHash":"f1efefa9ff9a58222ac1b08266e48e7b599ef8933fb429d9349dd73a202d6222","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52180","feedbackHash":"ac869f7d4d4acb058b5b2a291447e87f7cdb44f852ddc7f7fbff272b7f105bfe","nodeKey":"write_foundry_tests","submissionHash":"a297e5ffdb8b7d446eaa1171ed4506a728812247f5618975aca7924b7bf66399","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52270","feedbackHash":"4e98635a0489a526013e617ff720f7bbb3a1dd14bdd6c27bf67d775dd3801dba","nodeKey":"write_foundry_tests","submissionHash":"a9e09452763e2aa9d25709443b2fbb287c5c317d5f19b3726a63bcf553d8d737","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"3a33b22f4672851a61e4124f20c5ebb92759bb4eb4ec543b2b5b3f0eb3dd113c","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3bed38612db34f32","findings":[{"citation":"resolved","description":"The game hardcodes the Intake, the IMD asset, the action id `oracle.request@oracle-1` and the price 0.5 IMD as immutables/constants with no owner (by brief). The oracle-consumer reference says explicitly that a new action version (oracle-2) or a moved price must not need a redeploy; the README acknowledges the departure but claims that if IMD 'retires oracle-1 or moves the price, rounds stop settling and the sunset rule returns the pot'. That claim is false. `judge()` is the only function that moves a round out of `Open`; it calls `Intake.request(ACTION, ..., IMD, PRICE)` and reverts if the Intake reverts (ActionNotSold once oracle-1 is retired, or the pull/approve of 0.5 IMD is short once the price moves above it). `declareHungJury()` requires `Status.Pending` (line 255) and `_sunset()` is reached only from `_hung()` after seven Hung rounds. With `request` reverting, no round can ever become Pending, so no hung jury, no sunset, no `claimable` and no `sunsetShare` are ever produced. Every entry fee and the hook's 55% share of every future trading fee (pushed into `receive()` on every swap, forever) accumulate in a contract from which no path can release them. Trust gap: access (nobody can rotate the dependency) x economics (the pot keeps growing). Minimal fix that preserves the no-owner design: give a closed round that cannot be judged a public, time-gated exit (e.g. let `declareHungJury()` hang an Open round whose day is older than today by more than VERDICT_TIMEOUT, so the existing 7-round sunset returns the pot), and/or read the price with `Intake.priceOf(ACTION, IMD)` at judge time instead of a constant.","line":239,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\n\n/// @dev A stand-in for the IMD Intake after IMD retires `oracle-1`, moves the price above 0.5 IMD, or\n/// otherwise refuses the request: `request` reverts. Nothing in the game's constants can follow such a\n/// change (there is no owner, by design), so this is the state the README says the sunset rule covers.\ncontract RefusingIntake is IIntake {\n    error ActionNotSold();\n\n    function request(bytes32, bytes calldata, Callback calldata, address, uint256)\n        external\n        payable\n        returns (bytes32)\n    {\n        revert ActionNotSold();\n    }\n}\n\ncontract MintableIMD {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @title The pot is stranded for good when the Intake refuses `request`\n/// @notice `judge()` is the only way a round leaves `Open`; `declareHungJury()` needs `Pending`; the\n/// sunset rule needs seven `Hung` rounds. When `Intake.request` reverts, no round can ever become\n/// Pending, so no hung jury, no sunset and no claim: the pot (entry fees plus 55% of every trading fee,\n/// forever) is locked in the game with no path out. Fails on the current code; passes once a round that\n/// cannot be judged has a public, time-gated way to be settled or sunset without the Intake.\ncontract StrandedPotTest is Test {\n    uint256 constant START = 1_800_000_000;\n\n    MeatbagHerald herald;\n    MeatbagGame game;\n    RefusingIntake intake;\n    MintableIMD imd;\n    address alice = address(0xA11CE);\n    address keeper = address(0xC0FFEE);\n\n    function setUp() public {\n        vm.warp(START);\n        intake = new RefusingIntake();\n        imd = new MintableIMD();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), address(0x5598Aa9146215Bc13eb26f2c692Ad1461Fd32982));\n        assertEq(address(game), predictedGame);\n        imd.mint(keeper, 100 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(alice, 1 ether);\n    }\n\n    function test_potCanLeaveTheGameWhenTheIntakeRefusesEveryRequest() public {\n        // Trading fees have fed the pot and one human entered today's round.\n        (bool ok,) = address(game).call{value: 5 ether}(\"\");\n        assertTrue(ok);\n        uint256 day = game.today();\n        vm.prank(alice);\n        game.enter{value: 0.001 ether}(\"I burnt the toast again and I am not sorry.\");\n        assertEq(game.pot(), 5.001 ether);\n\n        // The round closes. From here on, every public call that could move the round is tried,\n        // once per verdict-timeout, for sixty days: far longer than seven hung juries would take.\n        vm.warp((day + 1) * 1 days + 1);\n        for (uint256 i = 0; i < 60; i++) {\n            vm.prank(keeper);\n            try game.judge() {} catch {}\n            try game.declareHungJury() {} catch {}\n            vm.prank(alice);\n            try game.claimSunset(day) {} catch {}\n            vm.prank(alice);\n            try game.claim() {} catch {}\n            vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\n        }\n\n        // Expected: the pot is no longer stranded; the entrant (the only one) can get it back by some\n        // public path. Actual on the current code: the round is still Open, the pot is still 5.001 ETH,\n        // alice has no claim and nothing has left the contract.\n        bool recovered = game.pot() < 5.001 ether || game.claimable(alice) > 0 || game.round(day).sunsetShare > 0\n            || alice.balance > 1 ether - 0.001 ether;\n        assertTrue(recovered, \"the pot is stranded: no public path settles, hangs or sunsets an unjudgeable round\");\n    }\n}","reproduction":"State: Intake.request reverts (IMD retired oracle-1 -> ActionNotSold, or price > 0.5 IMD). 1) Pot holds 5 ETH of trading fees; alice calls enter{value: 0.001 ether}(\"...\") on day D. 2) Warp to day D+1 and beyond. 3) judge() from any keeper reverts inside request(); declareHungJury() reverts NotPending(D); claimSunset(D) and claim() revert NothingToClaim. Repeat for 60 x VERDICT_TIMEOUT: identical. Expected (per README): rounds stop settling and the sunset rule returns the pot to entrants. Actual: round D stays Open forever, pot stays 5.001 ETH plus every future fee, nobody can ever withdraw. The scratch test test/scratch/StrandedPot.t.sol fails on the current code with 'the pot is stranded'.","severity":"high","snippet":"        intakeRequestId = IIntake(INTAKE)\n            .request(ACTION, judgeBody(day), IIntake.Callback(address(this), this.onOracleResult.selector), IMD, PRICE);","title":"Pot is stranded forever when the IMD Intake refuses request(): the sunset safety net only runs after judge() succeeds"},{"citation":"resolved","description":"Asymmetry between the two halves of the same fee. When ETH is the unspecified currency (exact-output buy, exact-input sell) `afterSwap` charges on `delta.amount0()`, the ETH the pool actually moved (line 259-261). When ETH is the specified currency (exact-input buy at line 224, exact-output sell at line 229) `beforeSwap` fixes the fee from `params.amountSpecified` before the pool runs and returns it as the specified hook delta; `afterSwap` then reads it back from transient storage unchanged (line 253) and takes it. Uniswap v4 fills a swap only up to `sqrtPriceLimitX96`; when the limit stops the swap early the pool moves a fraction of the specified amount, but the fee is still `rate` of the whole specified amount, and the PoolManager settles it against the swapper (their delta is swapDelta minus the hook delta). The brief says the fee is taken 'from the settled ETH delta'; here it is taken from an amount that never settled. Victim: any swapper (or router on their behalf) that passes a real price limit as slippage protection, which v4 explicitly supports; the excess goes to the pot, the swarm and the treasury. During the 30-minute decay the over-charge is up to 25% of the unfilled input. Fix: for the ETH-specified branches compute the fee in `afterSwap` from the ETH that actually settled, i.e. for an exact-input buy `poolEth = -delta.amount0()` with the hook refunding the difference between the pre-charged delta and `poolEth * rate / (10_000 - rate)`, or, simpler and complete, revert in `afterSwap` when `uint256(-delta.amount0()) + fee != uint256(-params.amountSpecified)` (buy) / `uint256(uint128(delta.amount0())) != uint256(params.amountSpecified) + fee` (sell) so a partial fill on the ETH side is refused rather than over-taxed. The exact-output sell at line 229 (`fee = amount * 10_000 / (10_000 - BASE_FEE_BPS) - amount;`) has the same defect: the pool paid 0.795 ETH but the fee was the gross-up of the 10 ETH asked.","line":224,"path":"src/MeatbagHook.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} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {MeatbagHook} from \"src/MeatbagHook.sol\";\nimport {MeatbagToken} from \"src/MeatbagToken.sol\";\nimport {MeatbagGame} from \"src/MeatbagGame.sol\";\nimport {HeartbeatTreasury} from \"src/HeartbeatTreasury.sol\";\n\n/// @title A partially filled swap whose ETH side is the specified currency is charged on the whole\n/// specified amount, not on the ETH that settled\n/// @notice The brief: the hook takes its fee \"from the settled ETH delta\". The beforeSwap branches\n/// (exact-input buy, exact-output sell) fix the fee from `params.amountSpecified` before the pool runs;\n/// when `sqrtPriceLimitX96` stops the swap early the pool moves far less ETH, but the fee stays. The\n/// afterSwap branches charge on the actual delta, so the two sides of the same fee are asymmetric.\n/// Fails on the current code. Passes once the hook either charges 2% of the ETH that actually settled\n/// or refuses the partial fill (either keeps the fee at 2% of settled ETH).\ncontract PartialFillProofTest is Test {\n    using StateLibrary for IPoolManager;\n\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    address constant SWARM = 0xd01122bBfFd00fc96252c8b29867a5359a3bca13;\n\n    IPoolManager manager;\n    MeatbagToken token;\n    MeatbagHook hook;\n    MeatbagGame game;\n    HeartbeatTreasury treasury;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        token = new MeatbagToken();\n        bytes memory creationCode =\n            abi.encodePacked(type(MeatbagHook).creationCode, abi.encode(address(manager), address(this)));\n        bytes32 initCodeHash = keccak256(creationCode);\n        for (uint256 i = 0; i < 500_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            address at;\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            require(at == predicted, \"hook deployment reverted\");\n            hook = MeatbagHook(payable(at));\n            break;\n        }\n        require(address(hook) != address(0), \"no salt\");\n        game = hook.game();\n        treasury = hook.treasury();\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12500, 60, IHooks(address(hook)));\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.deal(address(this), 1_000_000 ether);\n        token.approve(address(lpRouter), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n        lpRouter.modifyLiquidity{value: 2_000 ether}(\n            key, ModifyLiquidityParams(-887220, 887220, 1_000 ether, bytes32(0)), \"\"\n        );\n        vm.warp(block.timestamp + 1 hours); // decay over: 2% on buys\n    }\n\n    function feeTaken(uint256 potBefore, uint256 swarmBefore, uint256 treasuryBefore) internal view returns (uint256) {\n        return (game.pot() - potBefore) + (SWARM.balance - swarmBefore) + (address(treasury).balance - treasuryBefore);\n    }\n\n    function test_exactInputBuyStoppedByPriceLimitPaysTwoPercentOfSettledEth() public {\n        (uint160 sqrtPrice,,,) = manager.getSlot0(key.toId());\n        uint160 limit = uint160(uint256(sqrtPrice) * 999 / 1000);\n        uint256 ethBefore = address(this).balance;\n        (uint256 p, uint256 s, uint256 t) = (game.pot(), SWARM.balance, address(treasury).balance);\n\n        try swapRouter.swap{value: 10 ether}(\n            key, SwapParams(true, -int256(10 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 spent = ethBefore - address(this).balance; // what settled: pool ETH + fee\n            uint256 fee = feeTaken(p, s, t);\n            // Expected: fee is 2% of the ETH that actually settled (about 0.024 ETH on ~1.2 ETH).\n            // Actual: 0.2 ETH, 2% of the 10 ETH that were specified but never swapped.\n            assertLe(fee, spent * 200 / 10_000 + 2, \"fee charged on the unswapped part of the input\");\n        } catch {\n            // A hook that refuses partial fills on the ETH-specified side also keeps the fee honest.\n        }\n    }\n\n    function test_exactOutputSellStoppedByPriceLimitPaysTwoPercentOfSettledEth() public {\n        (uint160 sqrtPrice,,,) = manager.getSlot0(key.toId());\n        uint160 limit = uint160(uint256(sqrtPrice) * 1001 / 1000);\n        uint256 ethBefore = address(this).balance;\n        (uint256 p, uint256 s, uint256 t) = (game.pot(), SWARM.balance, address(treasury).balance);\n\n        try swapRouter.swap(\n            key, SwapParams(false, int256(10 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 received = address(this).balance - ethBefore;\n            uint256 fee = feeTaken(p, s, t);\n            uint256 gross = received + fee; // the ETH that actually left the pool\n            // Expected: 2% of what left the pool (about 0.02 ETH on ~1 ETH). Actual: 0.204 ETH, the\n            // gross-up of the 10 ETH that were asked for but never delivered.\n            assertLe(fee, gross * 200 / 10_000 + 2, \"fee charged on the undelivered part of the ask\");\n        } catch {}\n    }\n}","reproduction":"Pool seeded 1:1 with 1000e18 full-range liquidity, one hour after launch (buy fee 2%). Exact-input buy: swap{value: 10 ether}(key, SwapParams(zeroForOne=true, amountSpecified=-10e18, sqrtPriceLimitX96 = currentSqrtPrice*999/1000)). The pool fills only ~1.0137 ETH; the swapper's settled ETH delta is -1.213671899747849116e18 (pool 1.0137 + fee). Expected fee: 2% of the settled ETH, 0.024273437994956984e18. Actual fee: 0.2e18 (16.47% of what settled); the swapper over-pays 0.1757 ETH to pot/swarm/treasury. Exact-output sell: swap(key, SwapParams(false, +10e18, currentSqrtPrice*1001/1000)): the swapper receives 0.794919366347937776e18 ETH but the fee is 0.204081632653061224e18 (20.4% of the gross ETH out) against an expected 0.01998e18. test/scratch/PartialFillProof.t.sol fails on both on the current code.","severity":"medium","snippet":"            fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;","title":"Partially filled swaps with ETH as the specified currency are charged the fee on the whole specified amount, not on the settled ETH delta"},{"citation":"resolved","description":"Branch asymmetry between the two buy paths. An exact-input buy (beforeSwap, line 224) charges `rate` on the ETH the buyer sends, so at launch the buyer pays 25% of total spend. An exact-output buy (afterSwap, line 261) charges `rate` on `poolEth`, the ETH the pool took, and the buyer pays poolEth + fee, so the same `rate` yields rate/(1+rate) of total spend: 20.00% at launch instead of 25%, 11.9% instead of 13.5% at 15 minutes. The decay exists to tax launch snipers and send the surplus to the pot; any bot that specifies MEAT out instead of ETH in pays a fifth less tax for the same spend and the pot receives correspondingly less. After the decay the gap shrinks to 1.96% vs 2.00% (documented in the README), but the README does not document the launch-window gap. Fix: in the exact-output buy branch compute the fee on the gross spend, i.e. fee = poolEth * rate / (10_000 - rate), so both branches charge `rate` of what the buyer pays (the sell branches already do this: exact-output sells gross up at line 229).","line":261,"path":"src/MeatbagHook.sol","reproduction":"At launchedAt (buyFeeBps() == 2500), pool seeded 1:1 with 1000e18 liquidity. Exact-output buy of 1e18 MEAT: total ETH spent 1.267089874684811395e18, fee 0.253417974936962279e18 = 2000 bps of spend. Fresh identical pool, exact-input buy of the same 1.267089874684811395e18 ETH: fee 0.316772468671202848e18 = 2499 bps of spend. Expected: the same ETH spent at the same instant pays the same 25% launch tax. Actual: the exact-output buyer keeps 0.063354493734240569 ETH that the pot would have received from the exact-input branch (scratch test test/scratch/Leads.t.sol::test_exactOutputBuyPaysLessLaunchTaxForTheSameSpend).","severity":"low","snippet":"            fee = poolEth * rate / 10_000;","title":"Exact-output buys pay the launch decay tax on the pool amount, not on the ETH spent: a sniper picks the branch that pays 20% instead of 25%"},{"citation":"resolved","description":"`fundNextRun()` is public (as the brief requires) and the only gate is a 12-hour timer that is reset by whoever calls it, regardless of how much it sends. The brief's intent is that the heartbeat job can draw up to 0.01 ETH per 12 h. A third party who calls it the moment the treasury holds any nonzero dust sends that dust to the swarm and locks the full 12-hour window, so a subsequent fee deposit of any size cannot be drawn until the window passes. Repeated every 12 h, right after the first small trade of each window, this caps what the heartbeat actually receives at roughly the dust in the treasury at those moments rather than 0.01 ETH per run. The ETH is never lost (it stays in the treasury or reaches the swarm), so impact is a delay of the job funding, not a loss: low. Fix options that keep the public call and the cap: only reset the timer when the full cap is sent (or require amount == CAP_PER_RUN unless the balance is below it and at least N hours have passed), or let the timer lock proportionally to the amount sent.","line":38,"path":"src/HeartbeatTreasury.sol","reproduction":"1) One hour after launch, a buy of 0.00005 ETH leaves the treasury with 200000000000 wei (0.0000002 ETH). 2) address(0xBAD) calls fundNextRun(): returns 200000000000, sets lastRunAt. 3) A 10 ETH buy then deposits 0.04 ETH into the treasury. 4) fundNextRun() from the heartbeat reverts TooSoon(lastRunAt + 12h) for the next 12 hours even though 0.01 ETH is available. Expected: the heartbeat can draw up to 0.01 ETH per 12 h. Actual: this window paid 0.0000002 ETH (scratch test test/scratch/Leads.t.sol::test_dustCallConsumesTheHeartbeatSlot).","severity":"low","snippet":"        amount = address(this).balance;\n        if (amount > CAP_PER_RUN) amount = CAP_PER_RUN;\n        if (amount == 0) revert NothingToSend();\n        lastRunAt = block.timestamp;","title":"Anyone can burn the heartbeat's 12-hour slot with a dust balance, delaying funding of the swarm job"},{"citation":"resolved","description":"The brief's HERALD section says 'No caller-supplied text', while the HEARTBEAT section wants every heartbeat job to end with a Message. The implementation resolves this by letting the hardcoded swarm wallet post arbitrary text, documented in the README as the single exception. That is a design decision, not a bug, but it is a privileged capability with no on-chain bound: whoever holds that key (the IMD job runner) can emit any text as an official letter on the channel the site presents as the only source of news, including a fake migration or claim link, and nothing can revoke it (no owner, constant address). Recorded as a trust assumption for the judge; if the requester intended the stricter reading of 'No caller-supplied text', the heartbeat letter would need a fixed template or a different event.","line":63,"path":"src/MeatbagHerald.sol","reproduction":"vm.prank(0xd01122bBfFd00fc96252c8b29867a5359a3bca13); herald.post(\"MEATBAG v2 is live, migrate your tokens at 0x1234\") -> emits Message(TO, ...) and count becomes 2; any other sender reverts NotAuthorised (scratch test test/scratch/Leads.t.sol::test_swarmWalletPostsFreeText).","severity":"info","snippet":"    function post(string calldata text) external {\n        if (msg.sender != SWARM) revert NotAuthorised();\n        count++;\n        emit Message(TO, text);\n    }","title":"Trust assumption: the swarm wallet 0xd011..bca13 is the one key that can publish free text on the only official channel"}],"hash":"24276760dab282c3d66f5d76ee213912a5e95902b9b30dfc8e9166d68af2b7a8","nodeId":"c435efb2-bdf8-46ac-a58b-670eb7911f15","outcome":"completed","summary":"The review is complete. The findings file at `.imd-findings.json` holds five findings and a coverage row for all 21 entry points plus four invariants. No source files were changed; only `test/scratch/` and the findings file were written.\n\n**Findings, by severity**\n\n1. **High. The pot is stranded forever if the IMD Intake ever refuses `request()`.** The game hardcodes the action id, the price and the Intake with no owner. `judge()` is the only path out of `Open`, `declareHungJury()` needs `Pending`, and the sunset rule needs seven `Hung` rounds. If the Intake reverts (action retired, price moved), no round ever leaves `Open`, so the README's claim that the sunset rule returns the pot is false. Entry fees and 55% of every future trading fee lock in the game permanently. Proof test fails on the current code.\n\n2. **Medium. Partially filled swaps with ETH as the specified currency pay the fee on the whole specified amount.** The `beforeSwap` branches fix the fee from `amountSpecified` before the pool runs, while the `afterSwap` branches charge on the actual delta. A 10 ETH exact-input buy stopped by a price limit after about 1 ETH still pays a 0.2 ETH fee, which is 16.5% of what settled. An exact-output sell for 10 ETH that delivers 0.79 ETH pays 0.204 ETH. Proof test fails on both paths.\n\n3. **Low. Exact-output buys pay the launch tax on the pool amount, not the spend.** At launch an exact-input buy pays 25% of what is spent and an exact-output buy pays 20%. A sniper picks the cheaper branch and the pot receives less.\n\n4. **Low. Anyone can burn the heartbeat's 12-hour slot with a dust balance.** A call when the treasury holds dust sends the dust and locks the window, delaying the swarm's funding. No ETH is lost.\n\n5. **Info. Trust assumption on the swarm wallet.** It is the one key that can post free text on the only official channel, with no revocation. Documented in the README but it conflicts with the brief's \"no caller-supplied text\" line, so it is recorded for the judge.\n\n**Coverage.** Every callback checks the PoolManager, initialization is factory-only and once-only, the oracle callback is intake-only with domain-bound signatures and single consumption, claims are pull-only with effects before interaction, and no contract has an owner, setter, upgrade or pause. The static-analysis reentrancy and unsafe-cast leads were traced and rejected: the swarm call reenters into a zero balance, and the oversized fee cast is caught by the PoolManager's SafeCast before any delta is credited.","treeHash":null,"usage":{"cachedInputTokens":1728139,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":55440,"runtime":"claude","turns":37,"wallClockMs":873783}},{"artifacts":[],"attempt":1,"bundleHash":"33e2303dfa0b4b7d5acaa66ea942750b53d40f05d86f13adb0c66c9fff6366c7","device":"d00f790fc692b1a4","findings":[],"hash":"256b3c4779f6874676ecf58046169056e34a19ff06c771739c663c651d99874a","nodeId":"0906fe4d-e2b3-4a52-a746-c66a06b92da7","outcome":"completed","summary":"Committed on `main`; the working tree is clean, 515 files, no submodules (only `.gitattributes` files under `lib/`, which are ordinary files).\n\n## Summary\n\n**Delivered** a complete Foundry project for the MEATBAG `univ4_hook` launch on Ethereum mainnet. `forge build`, `forge test` (60 tests) and `forge fmt --check` all pass offline with solc 0.8.26 pinned, `bytecode_hash = \"none\"`, via-IR, cancun.\n\n**Contracts** (`src/`):\n- `MeatbagToken`: standard fixed-supply ERC-20 (MEATBAG / MEAT, 10^27 to the factory, no args, no admin).\n- `MeatbagHook(IPoolManager, address factory)`: 2% ETH fee on every swap, taken from the ETH side inside the swap for buy/sell × exact-in/exact-out (before/afterSwapReturnDelta). Buys decay 25%→2% over 30 minutes with the surplus to the pot. Every 2% base splits 55% pot / 25% swarm wallet / 20% heartbeat treasury. Takes native ETH when the PoolManager has it, mints an ERC-6909 claim otherwise, redeemed on the next swap or via `redeemClaims()`. `beforeInitialize` accepts only the factory, once, ETH-paired. Its constructor deploys the treasury, herald and game (CREATE, nonce-predicted wiring) so all four ship in the launch with no owner and no wiring step.\n- `MeatbagGame`: daily UTC rounds, ASCII ≤200 bytes, ≤40 entries, slot k = k×0.001 ETH; `judge()` pulls 0.5 IMD from the caller, pays the Intake for `oracle.request@oracle-1` (uint256, panel, 7/4, entries in definitions), credits the keeper 3% of the pot; canonical callback verifies the EIP-712 attestation (signer `0x5598…2982`, replay-consumed); winner gets 80% as a pull claim, rest carries over; timeout → hung jury; 7 unsettled → equal sunset claims.\n- `MeatbagHerald`: verbatim launch letter in the constructor, fixed automatic messages only (first trade, 1/10/100 ETH volume, first verdict, first hung jury, pot records); the swarm wallet alone may post each heartbeat's letter.\n- `HeartbeatTreasury`: public `fundNextRun()`, ≤0.01 ETH per call, ≥12 h apart, to `0xd011…bca13`.\n\n**Tests**: fee paths and decay, exact splits, fresh-manager claim path, entry pricing/limits, attestation checks (wrong sender, signer, domain, tampered, expired, not-yet-valid, replay), carry-over, sunset claims, heartbeat cap, herald texts, oracle conformance vector, deploy script, and a replica of the launch floor's opcode scan (no DELEGATECALL/SELFDESTRUCT in any runtime). The mainnet fork rehearsal against the real PoolManager passed (block 26155277), excluded from the default profile since the verifier has no network.\n\n**Also**: `site/index.html` (dark single page: entries, slot price, pot, judge, verdict history with panel agreement, hung juries, claims, buy/sell link, \"the swarm's letters\" feed, no-Twitter note; the heartbeat fills in the hook address after launch), `script/Deploy.s.sol` rehearsal script with no env reads, and a README covering assumptions, manifest parameters (`\"$poolManager\"`, `\"$factory\"`), and operational responsibilities.\n\n**Open items, documented in the README**: the independent adversarial review the brief lists is a post-submission step by another contributor; protocol addresses are immutable by the brief's no-owner rule (signer rotation ends settlement and triggers the sunset rule); \"repaid plus 3%\" was read as a 3%-of-pot ETH claim to the keeper at request time.","treeHash":"4cf5e4489108b4bf3c62f3b75432c7b6199e68f4","usage":{"cachedInputTokens":5362350,"inputTokens":1154,"model":"claude-fable-5-1","outputTokens":115410,"runtime":"claude","turns":62,"wallClockMs":1757291}},{"artifacts":[],"attempt":1,"bundleHash":"e6192cf032ca42e056adc80f5b9df42b90b533047f4ce6460d080661a897d294","device":"ce319efac2b76da0","findings":[],"hash":"2a6be31d8279e8d352698a642af9229c8031d5a8c0370668cb94f56a2c905e2b","nodeId":"88a67906-0f6f-48f4-a43a-ca6e6f023edd","outcome":"completed","summary":"Done. `launch.json` is written at the repository root and is the only tracked change.\n\n**What the manifest declares**\n- Hook `MeatbagHook` with constructor arguments `$poolManager` and `$factory`. There is no `$owner` because the brief forbids any owner, and no `$token` because the hook reads the launch token from the PoolKey in `beforeInitialize`.\n- Permissions copied from `getHookPermissions` in `src/MeatbagHook.sol:116`: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta (address flags 0x20CC).\n- Token `MeatbagToken`, name MEATBAG, symbol MEAT, 18 decimals.\n- Pool exactly as the launch gives it: paired currency the zero address (native ETH), fee 12500, tickSpacing 60, initialPrice 79228162514264337593543950336.\n- Notes summarise the fee, splits, settlement, game, herald and heartbeat. Every address in them is lowercase and none is a stand-in.\n\n**Verification**\n- The JSON parses, the first key is `\"kind\": \"univ4_hook\"`, and the notes are 3396 characters, under the 4000 cap.\n- `forge build` and `forge test` pass: 61 tests, 0 failures.\n- A scratch test in `test/scratch/` mined an address for the manifest's five flags, confirmed they equal what the hook's `getHookPermissions` returns, and confirmed the factory can initialize the ETH/MEAT pool at fee 12500 and spacing 60 through the hook's `beforeInitialize`. It also checked the token's name, symbol and decimals against the manifest.\n\nNo other file was touched. The git status shows only the new `launch.json`.","treeHash":"b66b1c145b21cff4d92ebe1886c373c97958c194","usage":{"cachedInputTokens":555001,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":7646,"runtime":"claude","turns":11,"wallClockMs":203311}},{"artifacts":[],"attempt":1,"bundleHash":"8e857e96ba241831a40d9561e623bc5844a3cd663104073257463cae1f048aa8","device":"d20c1a95c50699ea","findings":[],"hash":"38c6921b31fbf354f8a74304632de4f5918b43671229d790ecb7fbf4bc8babb6","nodeId":"0906fe4d-e2b3-4a52-a746-c66a06b92da7","outcome":"completed","summary":"I fixed the medium finding: a valid first verdict now lands within the oracle's 200,000 gas limit, even after the keeper has already claimed their reward. `forge build`, `forge fmt --check` and `forge test` all pass (76 tests), and the reviewer's proof test, run from `test/scratch/`, now passes.\n\n**The fix (`src/MeatbagGame.sol`).** The cause was the herald's \"first verdict\" letter, which the oracle callback was sending itself. That call no longer happens inside the callback. Instead, the letter is posted once, by whichever of these comes next after a round settles:\n- `judge()`\n- `declareHungJury()`\n- `sunset()`\n- `claim()` (usually the winner's)\n- a new public `announceFirstVerdict()`\n\nA new `firstVerdictAnnounced` flag makes sure it only goes out once. All of these run before anything else can move the game past the settled round, so the letter can't be skipped. The catch is that the letter now appears a little after the verdict rather than in the same transaction.\n\n**Tests and docs.** Three new tests in `test/MeatbagGame.t.sol`:\n- the reviewer's exact scenario (keeper claims, then the verdict arrives with exactly 200,000 gas);\n- the letter being posted once by `announceFirstVerdict()`;\n- the letter being posted by the next `judge()`.\n\nThe existing verdict test now expects the letter when the winner claims. The README describes the new timing.\n\nI couldn't rerun the proof against the old code myself, because git refuses to run in this repository (it flags the folder's ownership). The reviewer's run already showed it failing on the starting tree.\n\n**Advisory findings.** I left the code unchanged for all five and disputed each in `.imd-responses.json`:\n- **Strangers hanging a judgeable round (low):** the behaviour is real, but any deadline would have the same race at its last moment. Keepers have 25 hours after a round closes, and no funds are at risk.\n- **Heartbeat treasury can run more than once per 12 hours:** this was a deliberate fix from the last round, so a tiny payout can't lock out a full 0.01 ETH run. Total payout is still capped at 0.01 ETH plus one run per 12 hours. It does depart from the brief's literal \"at most once per 12 h\", so the requester should confirm it.\n- **The judge's IMD cost isn't repaid in ETH:** there's no on-chain IMD price, the brief names no ETH figure, and there's no owner to set one.\n- **One funder can buy all 40 slots:** this follows directly from the brief's slot rules, and the README documents the break-even.\n- **The swarm wallet can post any text:** the brief requires each heartbeat job to end with a free-text letter.\n\nThe last three are the requester's call if they want them changed.","treeHash":"62ed79dedf236781b9ca2d3c3bd82b925336cfdb","usage":{"cachedInputTokens":987656,"inputTokens":30,"model":"claude-opus-5-5","outputTokens":9512,"runtime":"claude","turns":15,"wallClockMs":181275}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4ebee02fc506f27c","findings":[{"citation":"resolved","description":"Every path that ends a round (Settled, Hung, and the sunset rule) begins with a successful IIntake.request inside judge(). judge() pulls and forwards the constant PRICE = 0.5 IMD. The live Intake (0x1397434cd35e8a9c8ac312a61d3a285eb31dea56, Ethereum) reverts request() whenever amount < priceOf(action, asset): simulated on 2026-10-09 with amount 0.4e18, it reverted with custom error 0xbc03bccd(0x6f05b59d3b20000 = 0.5e18 expected, 0x58d15e17628000 = 0.4e18 given); with 0.5e18 and 0.6e18 it proceeded to transferFrom; and an unknown action id reverted ActionNotSold (0x48231789). The oracle-consumer reference the project copied says the price and the action version are owner-settable on the IMD side and that 'a moved price must not need a redeploy'. The brief forbids an owner, so the game fixes PRICE, ACTION, INTAKE and the signer as constants/immutables (README 'Fixed protocol values'). The README claims that in that case 'rounds stop settling and the sunset rule returns the pot', but the sunset rule is reached only from _hung(), and _hung() is reachable only from a round in Status.Pending (declareHungJury / judge sweep / onOracleResult), i.e. only after a request succeeded. A round whose judge() reverts stays Open forever: declareHungJury() reverts NotPending(day), judge() reverts, unsettledStreak never increments, _sunset never runs. Meanwhile MeatbagHook._distribute keeps pushing the pot's 55% share of every swap fee into MeatbagGame.receive (pot += msg.value) with no way out: there is no sweep, no owner, no time-based fallback. The same dead end is reached if the Intake retires oracle-1 (ActionNotSold), if the IMD token becomes unobtainable, or economically if the pot is too small for anyone to spend 0.5 IMD on judge() (3% of a 0.001 ETH pot is 0.00003 ETH). Minimal fix that keeps the no-owner design: read the price from Intake.priceOf(ACTION, IMD) at call time and pull/approve that amount (as the reference consumer does), and add a time-based sunset for rounds that stay Open past a deadline (e.g. a round that is still Open N days after it closed can be declared hung by anyone), so a dead or re-priced oracle can never strand the pot.","line":237,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\n\n/// @dev The live Intake (0x1397…dea56, Ethereum) reverts `request` when `amount` is below\n/// `priceOf(action, asset)` (observed on 2026-10-09 with amount 0.4e18 vs price 0.5e18) and the\n/// reference says the price is owner-settable. This mock reproduces exactly that check.\ncontract PricedIntake is IIntake {\n    uint256 public price = 0.5 ether;\n    uint256 public count;\n\n    error WrongAmount(uint256 expected, uint256 got);\n\n    function setPrice(uint256 p) external {\n        price = p;\n    }\n\n    function priceOf(bytes32, address) external view returns (uint256) {\n        return price;\n    }\n\n    function request(bytes32, bytes calldata, Callback calldata, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        if (amount < price) revert WrongAmount(price, amount);\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        requestId = keccak256(abi.encode(\"intake\", ++count));\n    }\n}\n\ncontract IMDToken is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1_000 ether);\n    }\n}\n\ncontract JudgeBrickTest is Test {\n    PricedIntake intake;\n    IMDToken imd;\n    MeatbagGame game;\n    address keeper = address(0xC0FFEE);\n    address alice = address(0xA1);\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        intake = new PricedIntake();\n        imd = new IMDToken();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        MeatbagHerald herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), vm.addr(0xA11CE));\n        imd.transfer(keeper, 100 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(alice, 1 ether);\n        vm.deal(address(game), 0);\n    }\n\n    /// Fails today: once the Intake's price moves above the hard-coded 0.5 IMD, `judge()` reverts for\n    /// every round forever, so no round can ever become Pending, Hung or Settled, the sunset rule can\n    /// never fire, and the pot (fed by 55% of every trading fee) is stranded with no owner to fix it.\n    /// Passes once `judge()` reads `Intake.priceOf(ACTION, IMD)` and pulls/approves that amount.\n    function test_judgeSurvivesAnIntakePriceMove() public {\n        vm.prank(alice);\n        game.enter{value: 0.001 ether}(\"I burnt the toast again this morning\");\n        (bool ok,) = address(game).call{value: 5 ether}(\"\"); // trading fees pushed by the hook\n        assertTrue(ok);\n        vm.warp((game.today() + 1) * 1 days + 1 hours);\n\n        intake.setPrice(0.6 ether); // IMD moves the price; the keeper holds and approved 100 IMD\n\n        vm.prank(keeper);\n        game.judge();\n\n        assertEq(uint8(game.round(game.roundDays(0)).status), uint8(MeatbagGame.Status.Pending));\n        assertEq(imd.balanceOf(address(intake)), 0.6 ether, \"the keeper paid the live price\");\n    }\n}","reproduction":"State: Intake.priceOf(bytes32('oracle.request@oracle-1'), IMD) = 0.6e18 (IMD moves its price; the reference says to expect this). 1) alice: game.enter{value: 0.001 ether}('...') on day D. 2) The hook pushes trading fees: game.receive() adds e.g. 5 ETH to pot. 3) Day D+1, keeper (holds and approved 100 IMD): game.judge() -> Intake reverts WrongAmount(0.6e18, 0.5e18); the call reverts. 4) declareHungJury() reverts NotPending(D). No call sequence by anyone can ever move round D out of Status.Open, so pot (5.001 ETH) and every future fee share are locked permanently. Expected (brief/README): rounds stop settling and the sunset rule returns the pot to entrants. Actual: no sunset is reachable; funds stranded. The scratch proof test/scratch/JudgeBrick.t.sol fails today with WrongAmount(6e17, 5e17).","severity":"high","snippet":"        IERC20(IMD).safeTransferFrom(msg.sender, address(this), PRICE);\n        IERC20(IMD).forceApprove(INTAKE, PRICE);\n        intakeRequestId = IIntake(INTAKE)\n            .request(ACTION, judgeBody(day), IIntake.Callback(address(this), this.onOracleResult.selector), IMD, PRICE);","title":"judge() hard-codes the Intake price and the game has no fallback when a request cannot be made: the pot and 55% of all future fees are stranded forever"},{"citation":"resolved","description":"beforeSwap computes the fee from params.amountSpecified (exact-input buy: the whole ETH in; exact-output sell: the gross-up of the whole ETH asked for) and returns it as the specified hook delta. The PoolManager then swaps only amountSpecified minus that fee up to the caller's sqrtPriceLimitX96; if the limit stops the swap early, swapDelta covers only the consumed part but the hook delta (the fee on the full request) is charged in full. afterSwap re-reads the same fee from transient storage and settles it, so the overcharge is kept by the pot/swarm/treasury. The brief's and README's guarantee is '2% of the ETH moved'; here the fee can be any multiple of the ETH that moved. Reachable by any caller that passes a price limit (direct unlock callers, custom routers, PoolSwapTest; the Universal Router's V4 exact-in single uses MIN/MAX limits and would only hit this when a pool's liquidity is exhausted). The loss is the swapper's own and bounded by their request, so low. Fix: compute and take the fee in afterSwap from the actual delta (delta.amount0()) for all four cases, taking it from the ETH side with a positive unspecified delta where ETH is unspecified and, where ETH is specified, by settling the difference back (or document that price-limited partial fills are charged on the requested amount and recommend amountOutMinimum-style slippage instead of price limits).","line":224,"path":"src/MeatbagHook.sol","reproduction":"Pool at the 1:1 opening price with full-range liquidity (HookTestBase.setUpPool(true)), fee rate 2% (warp 1h after launch). swapRouter.swap{value: 100 ether}(key, SwapParams(zeroForOne=true, amountSpecified=-100 ether, sqrtPriceLimitX96=TickMath.getSqrtPriceAtTick(-60)), ...). Observed: the pool took 3.042 ETH, the hook took 2.000 ETH (65.7% of the ETH actually swapped). Same call at launch (rate 25%) with limit getSqrtPriceAtTick(-1): the pool took 0.0506 ETH and the hook charged 25.000 ETH. Expected: fee = 2% (or the decayed rate) of the ETH actually swapped, i.e. 0.061 ETH and 0.0127 ETH respectively. Scratch test: test/scratch/PartialFill.t.sol and PartialFill2.t.sol (log output).","severity":"low","snippet":"            fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;","title":"For ETH-specified swaps the fee is charged on the requested amount, not the amount the pool actually swapped: a price-limited partial fill pays up to the full fee on ETH that never traded"},{"citation":"resolved","description":"The brief says 'Caller is repaid from the pot plus 3% of it'. judge() credits only pot * 300 / 10_000 and pulls 0.5 IMD from the caller with no repayment. The README records this as an assumption ('the pot cannot repay IMD in kind without a price feed'), but the consequence is a liveness gap in the round state machine: rounds must be judged strictly in order (cursor), each judge() costs 0.5 IMD plus gas, and while 3% of the pot is worth less than that, no rational caller judges, the oldest round stays Open, every later round queues behind it, and (see finding 1) an Open round never times out or sunsets. The pot only grows through trading fees, so a quiet market can leave the game stuck for an unbounded time. Fix options within the no-owner design: repay the keeper's IMD cost as a fixed ETH amount set at construction (the brief's 'repaid from the pot'), or let judge() be skipped/sunset by time for rounds nobody judges.","line":233,"path":"src/MeatbagGame.sol","reproduction":"Day D: one entrant, game.enter{value: 0.001 ether}. pot = 0.001 ETH. Day D+1: judge() would credit the caller 0.001e18 * 300 / 10_000 = 3e13 wei (0.00003 ETH) and pull 0.5 IMD from them. Expected per brief: the caller is repaid the 0.5 IMD cost from the pot plus 3%. Actual: a net loss for the caller of 0.5 IMD + gas - 0.00003 ETH, so nobody calls; round D stays Open indefinitely and blocks every later round (nextRoundToJudge() == D).","severity":"low","snippet":"        uint256 reward = pot * KEEPER_BPS / 10_000;\n        pot -= reward;\n        _credit(msg.sender, reward);","title":"The judge caller is not repaid the 0.5 IMD the brief promises, only 3% of the pot; small pots therefore never get judged and the round backlog cannot clear"}],"hash":"504b380dd370fdf685bb74671c9f9987e602aff0b7467fc838e003054676f3bd","nodeId":"f7ceed6c-e224-41b0-a8b0-a077a2f29dbe","outcome":"completed","summary":"Findings file written and verified: the JSON parses, every snippet matches its file and line, and the proof test still fails on the current code with the expected error.\n\n**Summary of the review**\n\nI audited the control flow of the MEATBAG contracts (hook, game, herald, treasury, token, attestation library) against the Execution Trace, Periphery and First Principles guides. The build and all 60 project tests pass. Three defects are recorded in `.imd-findings.json`, with a coverage row for all 21 listed entry points plus four invariants.\n\n- **High, with proof.** The game's judge flow forwards a hard-coded 0.5 IMD to the Intake. I simulated the live mainnet Intake and it reverts when the amount is below its current price, and reverts for a retired action id. Since every round-ending path, including the sunset rule, starts from a successful request, a price rise or action retirement leaves the oldest round Open forever. The hook keeps pushing 55% of every swap fee into the pot with no way out and no owner to intervene. The proof under test/scratch fails today with the Intake's price error.\n- **Low.** For ETH-specified swaps the hook computes its fee on the requested amount in beforeSwap. With a price limit that stops the swap early, the fee is still charged in full. Measured: pool took 3.04 ETH, hook charged 2 ETH. At launch rate the pool took 0.05 ETH and the hook charged 25 ETH. The loss is the swapper's own and standard routers mostly avoid it.\n- **Low.** The judge caller receives only 3% of the pot and is never repaid the 0.5 IMD the brief promises. Small pots are never judged, and because rounds settle strictly in order, a backlog forms that nothing can clear.\n\n**Leads I checked and rejected**: the slither reentrancy lines on distribution (only the project's own contracts and the swarm wallet are called, state is updated first), the int128 fee cast (an absurd input only ends in a settlement revert), the hook's delta accounting in all four swap shapes, replay and cross-round delivery in the oracle callback, the sunset arithmetic, and the attestation library, which is byte-identical to the reference.\n\n**Not reached**: the site, the fork rehearsal against live mainnet state, and the manifest, which does not exist yet.","treeHash":null,"usage":{"cachedInputTokens":1072593,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":50807,"runtime":"claude","turns":30,"wallClockMs":931625}},{"artifacts":[],"attempt":1,"bundleHash":"cbfcb062a18227fb91ed470bfd975fa094da3b02f9e73a8b2e591814a7433d25","device":"e12f98dda6acc55f","findings":[],"hash":"57437623a5df5fe3a3eddfea5ea5d6b91091f3d9e0b2aabc5a5c6c7abe84f246","nodeId":"0906fe4d-e2b3-4a52-a746-c66a06b92da7","outcome":"completed","summary":"Revision complete. Both held proofs reproduced on the starting tree and pass on the result. Build, 72 tests, format check and the mainnet fork rehearsal all pass. Answers for all nine findings are in `.imd-responses.json`.\n\n**Fixed (6)**\n\n- **Stranded pot (high).** `declareHungJury()` now hangs a round nobody could judge once it is 25 hours past its close, so a refusing Intake or a retired action still leads to the sunset. `judge()` reads the Intake's `priceOf` through a separate interface and falls back to 0.5 IMD, leaving the proof's `RefusingIntake` compiling against the unchanged `IIntake`.\n- **Partial fills (medium).** When ETH is the specified side, `afterSwap` compares the settled ETH with the amount the fee was priced on and reverts with `PartialFill` if they differ. v4 cannot refund the ETH side inside the swap, so refusing is the only correct option. Volume is now recorded from the settled delta on every path.\n- **Exact-output buy base (low).** Fee is now the buy rate on the buyer's whole spend, matching exact-input buys.\n- **Sunset in the callback (low).** The oracle callback only marks the seventh round hung. A public `sunset()` splits the pot, and `judge()` and `declareHungJury()` settle a due sunset first.\n- **Hook donations (low).** Any balance above what is owed goes to the pot on the next distribution.\n- **Heartbeat dust (low).** The treasury closes for a time proportional to the amount sent, so a dust run no longer blocks the 0.01 ETH run.\n\n**Disputed (3), documented in the README as assumptions**\n\n- **IMD repayment.** The pot holds ETH and the cost is IMD. A fixed reimbursement is a value only the requester can name.\n- **40-slot capture.** Follows the brief's per-wallet and per-round limits.\n- **Swarm free text.** The brief requires each heartbeat to end with a Message, which needs this path.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| forge build | OK |\n| forge test | 72 passed |\n| forge fmt --check | OK |\n| Reviewer proofs | 3 of 3 pass |\n| Fork test, real PoolManager | 2 passed |\n\nThe site now approves the live judge price, shows a sunset button when due, and notes that price-limited trades are refused. No `.imd-blocked.json` and no `launch.json` were written.","treeHash":"d2006418c9c0155c9dd7689fadf9c1da6fbdedc5","usage":{"cachedInputTokens":2842798,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":72031,"runtime":"claude","turns":79,"wallClockMs":1029905}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"47f3603854a893a3","findings":[{"citation":"resolved","description":"Four specialists reported this: audit_flow, audit_economics, audit_permissions and audit_math (the last at medium). judge() (lines 237-240) is the only way a round leaves Open. It pulls the constant PRICE of 0.5 IMD and calls the immutable INTAKE with the constant ACTION. declareHungJury() and onOracleResult need Status.Pending, and _hung()/_sunset() are reached only from those. If the Intake reverts request(), judge() reverts every time. That happens if the price moves above 0.5 IMD (the oracle-consumer reference says it is owner-settable and 'must not need a redeploy'), if oracle-1 is retired (ActionNotSold), or if the IMD transfer fails. The round at the cursor then stays Open forever, unsettledStreak never grows and _sunset never runs. With no owner and no sweep, the pot is locked: every entry fee, plus the hook's 55% of every future swap fee that it keeps sending into receive(). The contract NatSpec (lines 33-35) and the README say the sunset rule returns the pot in this case. That holds only for signer rotation. Fix that keeps the no-owner design: let declareHungJury() hang an Open round once now > day + 1 days + VERDICT_TIMEOUT, or catch a failed request and mark the round hung. Also read the price with Intake.priceOf(ACTION, IMD) instead of a constant.","line":255,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\n\n/// @dev The IMD token, as a plain ERC-20.\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1_000 ether);\n    }\n}\n\n/// @dev An Intake that no longer sells `oracle.request@oracle-1` at 0.5 IMD: the action was retired\n/// (`oracle-2` replaced it) or its price moved. The oracle-consumer reference says both happen and\n/// must not need a redeploy; this game has no owner, so from then on every `request` reverts.\ncontract RefusingIntake is IIntake {\n    error ActionNotSold();\n\n    function request(bytes32, bytes calldata, Callback calldata, address, uint256)\n        external\n        payable\n        returns (bytes32)\n    {\n        revert ActionNotSold();\n    }\n}\n\n/// @notice Finding: a round that was never requested can never be hung, so the pot is stranded for\n/// good once the Intake refuses `request`. `judge()` reverts, `declareHungJury()` needs a Pending\n/// round, and the sunset rule only counts rounds that went Pending first. Entrant fees and the hook's\n/// 55% of every future trading fee keep flowing into a pot nobody can ever take out.\n///\n/// Fails on the current code: 30 days after the round closed, no public call moves the cursor past\n/// it. Passes once a round that stays unrequested past a deadline can be declared hung (and so\n/// counted toward the sunset) through a public call.\ncontract StrandedPotProof is Test {\n    uint256 constant START = 1_800_000_000;\n\n    address signer = address(0x5598Aa9146215Bc13eb26f2c692Ad1461Fd32982);\n    address keeper = address(0xC0FFEE);\n    address alice = address(0xA1);\n\n    RefusingIntake intake;\n    ProofIMD imd;\n    MeatbagHerald herald;\n    MeatbagGame game;\n\n    function setUp() public {\n        vm.warp(START);\n        intake = new RefusingIntake();\n        imd = new ProofIMD();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), signer);\n        assertEq(address(game), predictedGame);\n\n        imd.transfer(keeper, 100 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(alice, 10 ether);\n    }\n\n    function test_unrequestedRoundCanStillBeClearedSoThePotIsNotStrandedForever() public {\n        // Trading fees have fed the pot, and Alice enters today's round.\n        (bool ok,) = address(game).call{value: 5 ether}(\"\");\n        assertTrue(ok);\n        uint256 day = game.today();\n        vm.prank(alice);\n        game.enter{value: 0.001 ether}(\"I burnt the toast again, twice, and ate it anyway.\");\n\n        // The round closes. The Intake now refuses the fixed action/price, so judging is impossible.\n        vm.warp((day + 1) * 1 days + 1 hours);\n        vm.prank(keeper);\n        vm.expectRevert(RefusingIntake.ActionNotSold.selector);\n        game.judge();\n\n        // Long past any reasonable deadline (30 days), the round is still \"Open\" and nothing can move\n        // the game forward: not judge(), not declareHungJury(), so no hung jury, no sunset, no claim.\n        vm.warp(block.timestamp + 30 days);\n        assertEq(game.cursor(), 0, \"the cursor still points at the dead round\");\n\n        bool advanced;\n        try game.declareHungJury() {\n            advanced = true;\n        } catch {}\n        if (!advanced) {\n            vm.prank(keeper);\n            try game.judge() {\n                advanced = true;\n            } catch {}\n        }\n        assertTrue(advanced, \"no public call can clear a round the Intake refuses to take; the pot is stranded\");\n        assertEq(game.cursor(), 1, \"the dead round must be behind the cursor so the sunset rule can reach it\");\n        assertTrue(game.unsettledStreak() >= 1 || game.pot() < 5.001 ether, \"the round counts as unsettled\");\n    }\n}","reproduction":"Ran test/scratch/Proof_b87f8b965e1d.t.sol on this tree. It deploys a game whose intake reverts ActionNotSold, puts 5 ETH in the pot and has alice enter for 0.001 ETH on day D. On day D+1 judge() reverts. 30 days later declareHungJury() reverts NotPending(D), judge() still reverts and cursor == 0. Expected: some public call clears the round so the sunset rule can return the pot. Actual: FAIL 'no public call can clear a round the Intake refuses to take; the pot is stranded'. 5.001 ETH and every future fee share are locked.","severity":"high","snippet":"        if (r.status != Status.Pending) revert NotPending(day);","title":"Pot locked for good if the Intake refuses judge()'s request: a round that was never requested can never be hung, so the sunset rule never runs"},{"citation":"resolved","description":"Five reports merged into one: write_foundry_tests (high), audit_math, audit_permissions, audit_flow and audit_economics. beforeSwap fixes the fee from params.amountSpecified, for exact-input buys at line 224 and the exact-output sell gross-up at line 229. It returns that fee as the hook's specified delta. v4 stops a swap at sqrtPriceLimitX96 without reverting. afterSwap reads the same fee back from transient storage (line 253) and settles it without comparing it to delta.amount0(). It also records ethMoved/volume from amountSpecified (line 256), so herald volume milestones fire on ETH that never moved. The brief requires the fee 'from the settled ETH delta'. The loss falls on swappers who set a binding price limit, so medium rather than high. During the decay window it is 25% of the unfilled input. Fix: on these two paths, revert in afterSwap when the settled ETH differs from the priced amount, or recompute the fee from delta.amount0() and refund the difference. Record volume from the settled amount.","line":224,"path":"src/MeatbagHook.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} from \"v4-core/src/types/Currency.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {MeatbagHook} from \"src/MeatbagHook.sol\";\nimport {MeatbagToken} from \"src/MeatbagToken.sol\";\nimport {MeatbagGame} from \"src/MeatbagGame.sol\";\nimport {HeartbeatTreasury} from \"src/HeartbeatTreasury.sol\";\n\n/// @title Proof: a swap that stops at its price limit is charged on the requested amount, not the ETH that moved\n/// @notice When ETH is the specified currency (exact-input buy, exact-output sell) the hook computes its\n/// fee in `beforeSwap` from `amountSpecified` and keeps it whatever the pool fills. A swapper whose\n/// `sqrtPriceLimitX96` stops the swap early pays the full fee on ETH that never traded, and `volume`\n/// records ETH that never moved. Both cases below pass once the hook either charges on the settled ETH\n/// delta or refuses the partially filled swap.\ncontract PartialFillProofTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    address constant SWARM = 0xd01122bBfFd00fc96252c8b29867a5359a3bca13;\n\n    PoolManager manager;\n    MeatbagToken token;\n    MeatbagHook hook;\n    MeatbagGame game;\n    HeartbeatTreasury treasury;\n    PoolSwapTest swapRouter;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        token = new MeatbagToken();\n        bytes memory creationCode =\n            abi.encodePacked(type(MeatbagHook).creationCode, abi.encode(address(manager), address(this)));\n        bytes32 initCodeHash = keccak256(creationCode);\n        for (uint256 i = 0; i < 500_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            address at;\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            require(at == predicted, \"hook deployment reverted\");\n            hook = MeatbagHook(payable(at));\n            break;\n        }\n        require(address(hook) != address(0), \"no salt\");\n        game = hook.game();\n        treasury = hook.treasury();\n        swapRouter = new PoolSwapTest(manager);\n        PoolModifyLiquidityTest lpRouter = new PoolModifyLiquidityTest(manager);\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12500, 60, IHooks(address(hook)));\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.deal(address(this), 1_000_000 ether);\n        token.approve(address(lpRouter), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n        lpRouter.modifyLiquidity{value: 2_000 ether}(\n            key, ModifyLiquidityParams(-887220, 887220, 1_000 ether, bytes32(0)), \"\"\n        );\n        vm.warp(block.timestamp + 1 hours); // past the decay: the rate is 2%\n    }\n\n    function feesHeld() internal view returns (uint256) {\n        return game.pot() + SWARM.balance + address(treasury).balance + hook.owedPot() + hook.owedSwarm()\n            + hook.owedTreasury();\n    }\n\n    function test_exactInputBuyStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() public {\n        uint256 ethBefore = address(this).balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60); // stops after roughly 3 ETH of a 100 ETH order\n        try swapRouter.swap{value: 100 ether}(\n            key, SwapParams(true, -int256(100 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 paid = ethBefore - address(this).balance;\n            uint256 fee = feesHeld();\n            uint256 poolTook = paid - fee;\n            assertLt(poolTook, 10 ether, \"the limit should have stopped the swap early\");\n            // Expected: 2% of the ETH that actually traded (either side of it, within rounding).\n            assertLe(fee, paid * 200 / 10_000 + 1, \"fee exceeds 2% of the ETH the swapper paid\");\n            assertGe(fee, poolTook * 200 / 10_000 - 1, \"fee below 2% of the ETH the pool took\");\n            assertLe(hook.volume(), paid, \"volume counts ETH that never moved\");\n        } catch {\n            // A hook that refuses partially filled swaps also satisfies the brief.\n        }\n    }\n\n    function test_exactOutputSellStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() public {\n        uint256 ethBefore = address(this).balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(60);\n        try swapRouter.swap(\n            key, SwapParams(false, int256(100 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 received = address(this).balance - ethBefore;\n            uint256 fee = feesHeld();\n            uint256 poolPaid = received + fee;\n            assertLt(poolPaid, 10 ether, \"the limit should have stopped the swap early\");\n            assertLe(fee, poolPaid * 200 / 10_000 + 1, \"fee exceeds 2% of the ETH that left the pool\");\n            assertLe(hook.volume(), poolPaid, \"volume counts ETH that never moved\");\n        } catch {\n            // A hook that refuses partially filled swaps also satisfies the brief.\n        }\n    }\n}","reproduction":"Ran test/scratch/Proof_25f9df2bd8a8.t.sol on this tree: a fresh PoolManager with 1000e18 full-range liquidity at 1:1, 1 hour after launch (2%). (1) Exact-input buy of 100 ETH, limit getSqrtPriceAtTick(-60): the hook fee is 2e18. The test caps it at 2% of what the swapper paid, 100847677220089634, and fails: '2000000000000000000 > 100847677220089634'. (2) Exact-output sell of 100 ETH, limit getSqrtPriceAtTick(60): the fee is 2040816326530612244 against a 2% cap of 59907099118215619 on the ETH that left the pool. The seller receives about 0.95 ETH of the 3 ETH the pool paid. hook.volume() becomes 100 ETH after a fill of about 3 ETH.","severity":"medium","snippet":"            fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;","title":"When ETH is the specified side, the fee is charged on amountSpecified, not on the ETH that settled: price-limited partial fills overpay and an exact-output seller can end up paying ETH"},{"citation":"resolved","description":"Four reports merged into one: audit_math (medium), audit_economics, audit_permissions and write_foundry_tests (info). Exact-input buys pay rate x the ETH the buyer sends (line 224). Exact-output buys pay rate x the ETH the pool took, added on top of it. The effective rate is rate/(1+rate): 20.0% instead of 25% at launch and 1.96% instead of 2% after the decay. The sell side already grosses up (line 229). A launch sniper using exact-output pays a fifth less of the surplus the brief sends to the pot. Fix: fee = poolEth * rate / (10_000 - rate) on this branch.","line":261,"path":"src/MeatbagHook.sol","reproduction":"At launchedAt (buyFeeBps() == 2500), on the 1:1 pool from HookTestBase.setUpPool(true), swap exact-output 1e18 MEAT: SwapParams(true, +1e18, MIN_SQRT_PRICE+1). Traced: the pool takes 1.013671899747849116 ETH, the fee is 0.253417974936962279 ETH and the buyer spends 1.267089874684811395 ETH, so the fee is 2000 bps of spend. An exact-input buy of the same 1.267089874684811395 ETH pays 0.316772468671 ETH (2500 bps). The pot receives about 0.0634 ETH less per such trade.","severity":"low","snippet":"            fee = poolEth * rate / 10_000;","title":"Exact-output buys pay the buy rate on the pool's ETH, not the buyer's: 20% of spend instead of 25% at launch"},{"citation":"resolved","description":"Reported by audit_math and write_foundry_tests. On the seventh hung round in a row, onOracleResult -> _hung -> _sunset does the attestation check, the herald call and then 7 fresh sunsetShare SSTOREs (about 22.1k gas each) plus 14 cold round reads. Specialists measured about 292k-329k gas, above the writer's 200,000 stipend. The reference says such a callback is not retried. The round stays Pending until declareHungJury() after VERDICT_TIMEOUT (25 h), so entrants are paid a day late. No funds are lost. Fix: have the callback only mark the round hung, and run _sunset from a public call, or compute sunset shares lazily in claimSunset.","line":399,"path":"src/MeatbagGame.sol","reproduction":"Fund the pot with 7 ETH. Hang six one-entry rounds via judge() + warp VERDICT_TIMEOUT + declareHungJury(), so unsettledStreak == 6. Enter, judge the seventh round and deliver a validly signed attestation with agreed = 3 from INTAKE with gas 200_000. The call returns false, the round stays Pending and unsettledStreak stays 6. With unbounded gas it succeeds (first success measured at 291,874 gas).","severity":"low","snippet":"        if (unsettledStreak >= SUNSET_AFTER) _sunset();","title":"The seventh straight hung verdict delivered by the oracle runs _sunset inside the 200,000-gas callback and fails delivery"},{"citation":"resolved","description":"Three reports merged into one: write_foundry_tests, audit_math and audit_flow. The brief says 'Caller is repaid from the pot plus 3% of it'. The code credits only 3% of the pot and repays nothing for the 0.5 IMD it pulls (line 237). While the pot is small, judging loses the caller money. Rounds are judged strictly in cursor order, so an unjudged round blocks every later one. The README records this as a deliberate reading. It still breaks a stated guarantee, and the requester must choose: either reimburse in ETH (for example a fixed amount set in the constructor) or accept the change.","line":233,"path":"src/MeatbagGame.sol","reproduction":"Pot 0.001 ETH (one entry). The next day the keeper calls judge(): the keeper's IMD balance falls by 0.5e18 and claimable[keeper] == 3e13 wei. No other credit exists. Expected per brief: the 0.5 IMD cost repaid from the pot, plus 3%.","severity":"low","snippet":"        uint256 reward = pot * KEEPER_BPS / 10_000;","title":"judge() caller is not repaid the 0.5 IMD 'from the pot' as the brief says, only 3% of the pot"},{"citation":"resolved","description":"Reported by audit_math. receive() accepts ETH from anyone. _distribute (lines 342-370) sends min(owed, balance) to each recipient, and every owed wei is matched by a take, so any surplus balance is never paid out. There is no sweep and no owner. Fix: send any balance above the owed totals to the game (the pot).","line":112,"path":"src/MeatbagHook.sol","reproduction":"After the pool is set up: address(hook).call{value: 1 ether}('') succeeds. hook.distribute(), then a buy, a sell and distribute() again: owedPot, owedSwarm and owedTreasury are all 0 and address(hook).balance is still 1 ether. Expected: the donation reaches the pot.","severity":"low","snippet":"    receive() external payable {}","title":"ETH sent directly to the hook can never leave: _distribute pays only what is owed"},{"citation":"resolved","description":"Reported by audit_permissions. fundNextRun() resets the 12 h timer whatever amount it sends. A caller who runs it while the treasury holds a few wei sends that dust and blocks the 0.01 ETH run for 12 h, even after fees arrive. Repeated, this delays heartbeat funding indefinitely. No funds are lost. Fix: reset the timer only when the full CAP_PER_RUN is sent, or charge the window in proportion to the amount sent.","line":41,"path":"src/HeartbeatTreasury.sol","reproduction":"The treasury holds 2e11 wei after a tiny buy. address(0xBAD) calls fundNextRun() and gets 2e11. A 10 ETH buy then adds 0.04 ETH to the treasury. fundNextRun() reverts TooSoon(lastRunAt + 12h) for 12 hours, although 0.01 ETH is available. Expected: up to 0.01 ETH per 12 h reaches the heartbeat.","severity":"low","snippet":"        lastRunAt = block.timestamp;","title":"Anyone can use up the 12-hour heartbeat slot by funding a dust amount"},{"citation":"resolved","description":"Reported by audit_economics. The only per-person limit is one entry per address, as the brief specifies. Forty wallets fill a day at 00:00 UTC (sum of k x 0.001 ETH = 0.82 ETH), after which enter() reverts RoundFull for everyone else. Whichever index the panel picks, the funder wins 80% of the pot. The capture pays when the pot is above about 0.24 ETH. Sunset shares are per entry, so the same applies there. This follows the brief's parameters, so it is a design note and not a code defect.","line":194,"path":"src/MeatbagGame.sol","reproduction":"Pot 10 ETH. 40 fresh wallets each enter with nextSlotPrice() (0.82 ETH in total); nextSlotPrice() == 0 afterwards. Next day, judge() and a valid attestation naming any index 0..39: a funder wallet gets claimable of about 8.4 ETH.","severity":"info","snippet":"        if (hasEntered[day][msg.sender]) revert AlreadyEntered();","title":"One funder can buy all 40 slots of a round for 0.82 ETH and is certain to win it (and most of a sunset)"},{"citation":"resolved","description":"Reported by audit_permissions. The brief says 'No caller-supplied text' for the herald but wants each heartbeat job to end with a Message. The code allows SWARM to post free text and documents it. Recorded as a trust assumption: whoever holds that key can post any 'official' letter, and it cannot be revoked.","line":64,"path":"src/MeatbagHerald.sol","reproduction":"vm.prank(0xd01122bBfFd00fc96252c8b29867a5359a3bca13); herald.post('migrate at 0x1234') emits Message(TO, ...). Any other sender reverts NotAuthorised.","severity":"info","snippet":"        if (msg.sender != SWARM) revert NotAuthorised();","title":"The hard-coded swarm wallet can post any text on the only official channel"}],"hash":"76c8b3c58fbd0308a75d84d0dabe7bc27fb1209957996d9bfd5028f504fc3df8","nodeId":"d324c57d-6451-4349-ad02-f176c0dddef3","outcome":"completed","summary":"The review is done and `.imd-findings.json` is written. It holds 9 findings (one high, one medium, five low, two informational), merged from 25 specialist reports, plus a coverage row for each of the 21 entry points. Every path, line and snippet in it matches the tree. I ran the two specialist proofs that apply, and both fail on the current code for the reasons their findings give. I removed `test/scratch` afterwards and changed no other file.\n\n**Findings, most severe first:**\n1. **High: the pot can be locked forever.** `judge()` is the only way a round moves forward, and it uses a fixed Intake price (0.5 IMD) and a fixed action id. If the Intake ever refuses that request (price raised, `oracle-1` retired), `judge()` always reverts. `declareHungJury()` only accepts rounds that were already requested, so the 7-round sunset rule can never run. With no owner, the pot and 55% of every future swap fee stay locked. The NatSpec and README claim the sunset rule covers this case. Four specialists reported it; I attached their proof, which fails on this code.\n2. **Medium: partially filled swaps overpay the fee.** When ETH is the amount the swapper fixes (exact-input buys and exact-output sells), the fee is charged on the requested amount, not the ETH that actually moved. A swap stopped early by its price limit pays the full fee: in the proof, a buy that moved about 3 ETH paid a 2 ETH fee. An exact-output seller can receive far less than the pool paid out, or even owe ETH. The volume counter also counts ETH that never traded, so the herald's volume messages can fire early. Five reports merged; proof attached and failing.\n3. **Low: exact-output buys pay a lower launch rate.** The buy fee is charged on the pool's ETH instead of the buyer's, so it comes to 20% of spend at launch instead of 25%, and 1.96% instead of 2% afterwards. The pot loses that difference.\n4. **Low: the seventh hung verdict can't be delivered.** When the oracle callback hangs the seventh round in a row, it runs the sunset inline. That needs about 292k gas, more than the callback's 200k limit. Entrants still get paid, but only after a 25-hour timeout.\n5. **Low: the `judge()` caller isn't repaid.** The brief says the caller is repaid from the pot plus 3%; the code pays only the 3% and never repays the 0.5 IMD. The README calls this a deliberate reading, but it breaks the stated guarantee.\n6. **Low: ETH sent straight to the hook is stuck.** The hook only pays out what it owes, so a donation to it can never leave.\n7. **Low: anyone can block the heartbeat.** Calling `fundNextRun()` when the treasury holds only a few wei uses up the 12-hour window.\n8. **Info:** one person can buy all 40 slots of a round with 40 wallets for 0.82 ETH and is sure to win it. This follows the brief's rules, so it is a design note.\n9. **Info:** the hard-coded swarm wallet can post any text on the only official channel. This is a trust assumption, not a bug.\n\n**Dropped as not code defects:**\n- The treasury builds up more ETH than its 0.01 ETH-per-run cap can ever pay out (the cap is what the brief asks for).\n- Fees are skipped on MEAT pools that don't use this hook.\n- A sunset can pay out entry fees from later, not-yet-judged rounds (a consequence of having one pot).\n\nMost static-analysis warnings were reentrancy into trusted contracts or timestamp use, and none reproduced as defects.\n\n**Deployment checks:** contract sizes are within limits (hook creation code 25,725 bytes, game runtime 11,781). The manifest's constructor arguments (`$poolManager`, `$factory`) and its five permissions match the code. `beforeInitialize` accepts the 12500 fee and only the factory as caller.\n\nOn coverage, 12 entry points have a finding. The other 9 hold: the claim paths, `receive()` functions, herald announcements, `distribute`, `redeemClaims`, `unlockCallback`, `beforeInitialize` and the standard ERC-20 functions.","treeHash":null,"usage":{"cachedInputTokens":814487,"inputTokens":16,"model":"claude-opus-5-5","outputTokens":16009,"runtime":"claude","turns":12,"wallClockMs":250021}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3f91b58cf7cd2d45","findings":[{"citation":"resolved","description":"Every exit from the pot (80% prize, 3% keeper reward, sunset shares) requires a round to pass through Status.Pending, and the only way into Pending is judge() succeeding at line 239-240, which calls the fixed INTAKE with the fixed ACTION, the fixed IMD asset and the fixed PRICE (0.5 IMD). declareHungJury() (line 255) refuses any round that is not Pending, and _hung()/_sunset() are reachable only from a Pending round. The oracle-consumer reference states these values are owner-settable on purpose because 'a new action version (oracle-2) or a moved price must not need a redeploy'; this game has no owner and hardcodes them. The moment the Intake stops selling oracle.request@oracle-1 at exactly 0.5 IMD (action retired, price raised, intake replaced, IMD paused), judge() reverts on every call, the round at the cursor stays Open forever, the sunset counter never moves, and the pot is locked for good while the hook keeps pushing 55% of every trading fee into it (MeatbagGame.receive, line 160-163). The README's assumption 'if IMD ... retires oracle-1 or moves the price, rounds stop settling and the sunset rule returns the pot' is false for those two cases: the sunset rule only counts rounds that were requested first. Signer rotation is the only case it covers. Fix that keeps the no-owner design: let declareHungJury() (or judge()) also hang a round that has been closed for longer than VERDICT_TIMEOUT without ever being requested (status Open and day + 1 days + VERDICT_TIMEOUT < block.timestamp), so seven such rounds still trigger _sunset() and the entrants recover the pot. Alternatively wrap the request in try/catch and hang on failure.","line":255,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\n\n/// @dev The IMD token, as a plain ERC-20.\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1_000 ether);\n    }\n}\n\n/// @dev An Intake that no longer sells `oracle.request@oracle-1` at 0.5 IMD: the action was retired\n/// (`oracle-2` replaced it) or its price moved. The oracle-consumer reference says both happen and\n/// must not need a redeploy; this game has no owner, so from then on every `request` reverts.\ncontract RefusingIntake is IIntake {\n    error ActionNotSold();\n\n    function request(bytes32, bytes calldata, Callback calldata, address, uint256)\n        external\n        payable\n        returns (bytes32)\n    {\n        revert ActionNotSold();\n    }\n}\n\n/// @notice Finding: a round that was never requested can never be hung, so the pot is stranded for\n/// good once the Intake refuses `request`. `judge()` reverts, `declareHungJury()` needs a Pending\n/// round, and the sunset rule only counts rounds that went Pending first. Entrant fees and the hook's\n/// 55% of every future trading fee keep flowing into a pot nobody can ever take out.\n///\n/// Fails on the current code: 30 days after the round closed, no public call moves the cursor past\n/// it. Passes once a round that stays unrequested past a deadline can be declared hung (and so\n/// counted toward the sunset) through a public call.\ncontract StrandedPotProof is Test {\n    uint256 constant START = 1_800_000_000;\n\n    address signer = address(0x5598Aa9146215Bc13eb26f2c692Ad1461Fd32982);\n    address keeper = address(0xC0FFEE);\n    address alice = address(0xA1);\n\n    RefusingIntake intake;\n    ProofIMD imd;\n    MeatbagHerald herald;\n    MeatbagGame game;\n\n    function setUp() public {\n        vm.warp(START);\n        intake = new RefusingIntake();\n        imd = new ProofIMD();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), signer);\n        assertEq(address(game), predictedGame);\n\n        imd.transfer(keeper, 100 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(alice, 10 ether);\n    }\n\n    function test_unrequestedRoundCanStillBeClearedSoThePotIsNotStrandedForever() public {\n        // Trading fees have fed the pot, and Alice enters today's round.\n        (bool ok,) = address(game).call{value: 5 ether}(\"\");\n        assertTrue(ok);\n        uint256 day = game.today();\n        vm.prank(alice);\n        game.enter{value: 0.001 ether}(\"I burnt the toast again, twice, and ate it anyway.\");\n\n        // The round closes. The Intake now refuses the fixed action/price, so judging is impossible.\n        vm.warp((day + 1) * 1 days + 1 hours);\n        vm.prank(keeper);\n        vm.expectRevert(RefusingIntake.ActionNotSold.selector);\n        game.judge();\n\n        // Long past any reasonable deadline (30 days), the round is still \"Open\" and nothing can move\n        // the game forward: not judge(), not declareHungJury(), so no hung jury, no sunset, no claim.\n        vm.warp(block.timestamp + 30 days);\n        assertEq(game.cursor(), 0, \"the cursor still points at the dead round\");\n\n        bool advanced;\n        try game.declareHungJury() {\n            advanced = true;\n        } catch {}\n        if (!advanced) {\n            vm.prank(keeper);\n            try game.judge() {\n                advanced = true;\n            } catch {}\n        }\n        assertTrue(advanced, \"no public call can clear a round the Intake refuses to take; the pot is stranded\");\n        assertEq(game.cursor(), 1, \"the dead round must be behind the cursor so the sunset rule can reach it\");\n        assertTrue(game.unsettledStreak() >= 1 || game.pot() < 5.001 ether, \"the round counts as unsettled\");\n    }\n}","reproduction":"State: pot = 5 ETH of trading fees; Alice enters day D (0.001 ETH). Intake.request now reverts (ActionNotSold / price moved). 1) Day D+1: keeper (holding and having approved 0.5 IMD) calls judge() -> reverts with the Intake's error; round D stays Open. 2) Warp 30 days. declareHungJury() -> NotPending(D). judge() -> still reverts. claimSunset(D) -> NothingToClaim. Expected: after a deadline some public call clears the round so the sunset rule can return the pot. Actual: cursor stays 0 forever, pot (5.001 ETH plus every future fee) is unrecoverable by anyone. See test/scratch/StrandedPot.t.sol, which fails on this tree.","severity":"high","snippet":"        if (r.status != Status.Pending) revert NotPending(day);","title":"Pot is stranded forever once the Intake refuses request(): an unrequested round can never be hung, so no sunset ever reaches it"},{"citation":"resolved","description":"The only per-entrant limit is one entry per address (line 194) and the only cost is the slot ladder (line 195: slot k costs k x 0.001 ETH, 0.82 ETH for all 40). Nothing ties an entry to a person. Economics with a pot P: the attacker fills all 40 slots the moment the UTC day starts (pot becomes P + 0.82), no human can enter that day (nextSlotPrice() returns 0, enter() reverts RoundFull), next day the attacker also calls judge() (3% of the pot as keeper) and whichever index the panel picks is one of their wallets, worth 80% of the remaining pot at callback time. Net profit = 0.776 P - 0.184 ETH, positive for P > 0.237 ETH; the pot is fed 1.1% of all ETH volume so it crosses that quickly. The sunset rule makes it worse because its split is per entry, not per person: once six rounds are hung (oracle silent), filling the seventh with 40 entries takes 40/(40 + honest entries) of the whole pot with no panel judgement at all. This follows the brief's parameters (one per wallet, 40 slots, k x 0.001 ETH), so it is a design-level economic risk rather than a coding slip; it is reported because the audit was asked for the economics and the capture is cheap, certain and repeatable every day. Mitigations that keep the design: a per-round entry fee that scales with the pot (e.g. slot price = max(k x 0.001 ETH, pot / 400)), a cap on the share of one round's slots a single funder can take (hard without identity), or a prize cap per round.","line":194,"path":"src/MeatbagGame.sol","reproduction":"State: pot = 10 ETH (donation or fees). 1) 40 fresh wallets each call enter(text) with the exact nextSlotPrice(); total paid 0.82 ETH; nextSlotPrice() == 0. 2) Next day attacker calls judge(): claimable[attacker] = 0.3246 ETH. 3) Intake delivers a valid attestation with index 17 (any index 0..39): claimable[wallet17] = 8.39632 ETH. Attacker spent 0.82 ETH + 0.5 IMD and receives 8.72 ETH. Sunset variant: 6 single-entrant hung rounds then a 40-entry round hung: share per entry 0.193 ETH, attacker 7.72 ETH of the 10 ETH pot, the six honest entrants 1.16 ETH together. Both measured in test/scratch/Capture.t.sol.","severity":"medium","snippet":"        if (hasEntered[day][msg.sender]) revert AlreadyEntered();","title":"A whole round can be bought: 40 Sybil wallets for 0.82 ETH guarantee 80% of the pot (and the lion's share of a sunset), profitable whenever the pot exceeds about 0.24 ETH"},{"citation":"resolved","description":"For exact-input buys and exact-output sells the fee is fixed in beforeSwap from params.amountSpecified (lines 224 and 229) and credited to the hook as a BeforeSwapDelta before the pool runs. If the swap stops at the user's sqrtPriceLimitX96 the pool consumes only part of the specified amount, but the hook still takes the fee on all of it, afterSwap still settles that full fee (line 253-256) and records the unfilled amount as volume (line 267). The stated invariant is 'its fee in ETH from the settled ETH delta ... 2%'. On a partial fill the effective rate is unbounded: in the reproduction a 100 ETH exact-input buy with a one-tick limit moved 3.04 ETH and paid 2 ETH of fee (66%). For an exact-output sell the gross-up fee (line 229) can exceed the ETH actually delivered, so the seller ends the swap having given tokens and received almost nothing (or even owing ETH if the fill is smaller than the fee). The volume counter also reports 100 ETH for a 3 ETH trade, which can trip the herald's 1/10/100 ETH letters cheaply. Routers that pass MIN/MAX price limits are unaffected; any wallet, aggregator or limit-order flow that sets a binding sqrtPriceLimitX96 overpays. Fix: in afterSwap recompute the fee from the actual ETH delta (delta.amount0 plus the hook's specified delta) and refund the excess as a negative unspecified delta, or document that price-limited fills are charged on the specified amount and that exact-output sells must not use a binding limit.","line":224,"path":"src/MeatbagHook.sol","reproduction":"Pool seeded 1:1 full range, 1 hour after launch (2%). swapRouter.swap{value: 100 ether}(key, SwapParams(true, -100e18, TickMath.getSqrtPriceAtTick(-60)), ...). Actual: pool took 3.042 ETH, fee taken 2.000 ETH (pot+swarm+treasury), swapper's ETH delta -5.042 ETH, hook.volume() == 100 ETH. Expected: fee 2% of 3.042 ETH = 0.0608 ETH, volume 3.042 ETH. Exact-output sell: SwapParams(false, +100e18, getSqrtPriceAtTick(60)): fee 2.0408 ETH, seller gave 3.042 MEAT and received 0.9545 ETH; expected fee 2% of ~2.995 ETH gross = 0.06 ETH. Measured in test/scratch/PriceLimit.t.sol.","severity":"low","snippet":"            fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\n        } else {\n            // Sell, exact output: the user wants exactly `amount` ETH; the pool pays out the gross so\n            // that the fee is 2% of what left the pool.\n            uint256 amount = uint256(params.amountSpecified);\n            fee = amount * 10_000 / (10_000 - BASE_FEE_BPS) - amount;","title":"Fee on the beforeSwap path is charged on the full specified amount, not on what the pool actually moved: a price-limited partial fill pays up to the whole 2% (or 25%) on ETH that never traded"},{"citation":"resolved","description":"Exact-input buys charge rate x (ETH the buyer sends) (line 224: 25% of spend at launch). Exact-output buys charge rate x (ETH the pool took) (line 261), and the buyer's total spend is take + fee, so the fee is rate/(1+rate) of spend: 20.0% at launch, 11.9% at the 13.5% point, 1.96% at steady state. The brief's rule is a single number for buys ('first 30 min decays linearly 25% to 2%'); two buyers spending the same ETH in the same block pay different fees depending on how they phrase the swap, and the difference is exactly the launch surplus that is supposed to go to the pot. A sniper spending 1.267 ETH via exact-output pays 0.2534 ETH; via exact-input they would pay 0.3168 ETH; the pot (which receives all of the surplus above 2%) loses 0.063 ETH per such buy. At steady state the gap is 0.04 points and immaterial; during the decay window it is up to 5 points. Fix: on the afterSwap buy path charge fee = poolEth x rate / (10_000 - rate) so the fee is rate x total spend, matching the exact-input path (the sell path already grosses up this way at line 229).","line":261,"path":"src/MeatbagHook.sol","reproduction":"At launchedAt (buyFeeBps() == 2500): buyExactOut(1 ether tokens, 5 ether budget) on the 1:1 pool. Actual: spent 1.2671 ETH, fee 0.2534 ETH = 2000 bps of spend (2500 bps of the pool's take). Expected (brief, and what buyExactIn charges): 2500 bps of the ETH spent = 0.3168 ETH. Measured in test/scratch/DecayAsym.t.sol.","severity":"low","snippet":"            fee = poolEth * rate / 10_000;","title":"During the 30-minute decay an exact-output buy pays the rate on the pool's take instead of on the ETH spent: 20% of spend at launch instead of 25%, so launch snipers route around a fifth of the surplu"},{"citation":"resolved","description":"fundNextRun() is the treasury's only outbound path and is capped at 0.01 ETH per 12 hours (lines 37-39). The treasury receives 20% of every 2% base fee, i.e. 0.4% of ETH volume. At 10 ETH of daily volume the inflow (0.04 ETH/day) already exceeds the maximum outflow (0.02 ETH/day); at 100 ETH/day 0.38 ETH/day is locked for good, with no owner, sweep or redirect. This follows the brief ('at most 0.01 ETH per call'), so it is recorded as a trust/design note, not a defect: the author should confirm that a permanently growing, unspendable balance is intended, or route the excess (e.g. anything above a reserve of N runs) to the pot.","line":39,"path":"src/HeartbeatTreasury.sol","reproduction":"Send 1 ETH to the treasury. fundNextRun() sends 0.01 ETH to SWARM; a second call in the same 12 hours reverts TooSoon. Over any period the treasury can release at most 0.02 ETH per day, so everything received above that stays. Concrete: 100 ETH of daily volume for 30 days puts 12 ETH into the treasury (20% of 2% of 3000 ETH) and lets at most 0.6 ETH out.","severity":"info","snippet":"        if (amount > CAP_PER_RUN) amount = CAP_PER_RUN;","title":"HeartbeatTreasury can only ever release 0.02 ETH per day, so 20% of all fees above that accumulates with no outlet"},{"citation":"resolved","description":"MeatbagToken is a plain ERC-20 and the hook refuses every pool but the one the factory opened (line 216 and beforeInitialize lines 155-156). Anyone can list MEAT against ETH elsewhere; once that venue has liquidity, traders and aggregators route there and the 2% (and the launch-window 25%) is avoided entirely. The economics of the pot therefore rest on the hooked pool keeping the deepest liquidity, which nothing enforces. This is inherent to a 'standard token plus hook' launch and is noted so the requester weighs it against a token-side fee; no code change is proposed.","line":216,"path":"src/MeatbagHook.sol","reproduction":"Initialize PoolKey(ETH, MEAT, 3000, 60, IHooks(address(0))) on the same PoolManager, add liquidity, swap: no FeeTaken event, pot unchanged. The hooked pool's beforeSwap is never involved.","severity":"info","snippet":"        if (PoolId.unwrap(key.toId()) != PoolId.unwrap(poolId)) revert NotThisPool();","title":"The hook fee, and so the pot and the heartbeat, are collected only on the hooked pool: a MEAT/ETH pool on v2, v3 or a hookless v4 key pays nothing"}],"hash":"80e02942891e0769fe9ada731255bbe4f4cd47c13d0dc0665b586e4f75b16f73","nodeId":"648928c7-a4d4-4531-99f4-328a70a68d03","outcome":"completed","summary":"The findings file is in place at the repo root (ignored by git by the harness's own `.gitignore`, as are the scratch tests, so no tracked file changed). The review is complete.\n\n**Outcome:** 6 findings written to `.imd-findings.json` with coverage rows for all 21 entry points plus 3 invariants. One high finding carries a Foundry proof that fails on the current tree.\n\n**Findings, most severe first**\n\n1. **High. Pot stranded forever if the Intake refuses `request()`.** The only way a round leaves `Open` is a successful `judge()`, which calls the hardcoded intake with the hardcoded action and 0.5 IMD price. `declareHungJury` refuses non-Pending rounds, so if IMD retires `oracle-1` or moves the price (the oracle-consumer reference says both are expected), no round can ever be hung, the sunset rule never fires, and the pot plus 55% of all future trading fees is locked with no owner to intervene. The README claims the sunset rule covers this case. It does not. Proof: `test/scratch/StrandedPot.t.sol` fails on this tree.\n\n2. **Medium. A round can be bought.** Forty Sybil wallets cost 0.82 ETH, lock humans out for the day, and guarantee the 80% prize. On a 10 ETH pot the attacker nets 7.9 ETH. The sunset split is per entry, so the same trick takes 7.7 of 10 ETH once the oracle is silent. This follows the brief's parameters, so it is flagged as a design-level economic risk with numbers.\n\n3. **Low. Price-limited partial fills overpay on the beforeSwap fee path.** The fee is fixed on `amountSpecified` before the pool runs. A 100 ETH exact-in buy with a tight limit moved 3.04 ETH and paid 2 ETH of fee. Volume is also inflated to 100 ETH.\n\n4. **Low. Exact-output buys pay the decay rate on the pool's take, not on ETH spent.** At launch that is 20% of spend instead of 25%, so snipers route around part of the surplus meant for the pot.\n\n5. **Info.** The heartbeat treasury can release at most 0.02 ETH per day. Everything above that accumulates with no outlet.\n\n6. **Info.** The fee is collected only on the hooked pool. Any other MEAT/ETH venue pays nothing.\n\n**Verified as holding:** the 55/25/20 split and hook delta accounting in all four swap shapes, the claims path on a fresh manager, the game's balance conservation (`pot + totalClaimable`), attestation checks, replay protection, carry-over, sunset share arithmetic, and the callback staying under the 200k stipend (the seventh hung inside the callback runs out of gas as the README documents and recovers through `declareHungJury`).\n\n**Not reached:** the mainnet fork test (needs network) and the 25-hour verdict timeout race against a late-but-valid attestation, which I could not substantiate without real panel latency data.","treeHash":null,"usage":{"cachedInputTokens":1679787,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":59696,"runtime":"claude","turns":31,"wallClockMs":955604}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7591760a616c6429","findings":[{"citation":"resolved","description":"Settles my earlier medium finding e95ca1f1 (first verdict ran out of the 200,000 gas stipend when totalClaimable was zero). onOracleResult no longer calls herald.announce(FIRST_VERDICT); _announceFirstVerdict() posts it once, guarded by firstVerdictAnnounced, from judge(), declareHungJury(), sunset(), claim() and the public announceFirstVerdict(). I traced every path that moves the cursor: judge() and declareHungJury() announce before moving it, and onOracleResult can only move it past a round judge() already made Pending, so the letter cannot be skipped and never lands in the callback. Measured minimum gas to deliver (test/scratch/GasProbe2.t.sol, binary search): first verdict with the keeper already claimed 171,477 (was 206,156), first hung jury in the callback 150,574, seventh hung in the callback 87,349, verdict after six hung rounds 157,177. All 129 non-fork tests pass, including the author's new test_firstVerdictFitsTheStipendAfterTheKeeperClaimed and the invariant that stipendFailures stays 0. Confirmed fixed; nothing introduced.","line":468,"path":"src/MeatbagGame.sol","reproduction":"Copied .imd/reads/proofs/Proof_e95ca1f14686.t.sol to test/scratch/ and ran forge test --match-path on it: PASS (it failed on the previous tree). Sequence: entrant enters for 0.001 ETH, warp 30 h, keeper judge() then claim() so totalClaimable == 0, intake delivers a valid verdict with call{gas: 200_000}: returns true, round Settled, claimable[entrant] > 0, firstVerdictAnnounced still false; the entrant's claim() then emits the first-verdict Message once.","severity":"info","snippet":"    function _announceFirstVerdict() internal {","title":"Settled (fixed): the first verdict now lands under the oracle writer's 200,000 gas stipend after the keeper has claimed; the herald's first-verdict letter is posted by the next public call"},{"citation":"resolved","description":"Settles my earlier low finding 00eb8c6b. The behaviour is unchanged and reproducible, and the author's answer holds: any finite deadline for a round nobody judged overlaps the last moment it could still be judged, so a longer window only moves the race; the deadline is what keeps the sunset reachable when the Intake refuses requests (the stranded-pot fix). Keepers have the full 25 hours after a round closes, no funds are lost and the pot carries over. Downgraded from low to a recorded design choice; the requester can ask for a longer window if they want more margin.","line":296,"path":"src/MeatbagGame.sol","reproduction":"Day D: alice enters. Warp to (D + 2) * 1 days + 1 hours (== hungJuryAt()). A snapshot shows keeper's judge() succeeds. Instead address(0xBAD) calls declareHungJury() first: round D is Hung, unsettledStreak == 1, and judge() in the same block reverts NothingToJudge.","severity":"info","snippet":"        if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);","title":"Settled (accepted as design): declareHungJury() can hang an unjudged round from hungJuryAt() while judge() is still valid on it"},{"citation":"resolved","description":"Settles my earlier info note 6b6c437d. Proportional closing was the fix for the dust-call finding and is intentional: a run that sent amount closes the treasury for 12 h * amount / 0.01 ETH, so at most 0.01 ETH plus one run leaves per 12 hours and it only ever goes to the swarm wallet. The README and launch.json notes both state the departure from the brief's literal 'at most once per 12 h'. Recorded for the requester; not a defect.","line":41,"path":"src/HeartbeatTreasury.sol","reproduction":"Deal the treasury 1 wei; fundNextRun() sends it and nextRunAt == block.timestamp. Deal 1 wei again; fundNextRun() in the same block succeeds. Deal 0.01 ETH; fundNextRun() sends 0.01 ETH and a second call within 12 h reverts TooSoon.","severity":"info","snippet":"        nextRunAt = block.timestamp + INTERVAL * amount / CAP_PER_RUN;","title":"Settled (accepted as design): fundNextRun() may run more than once per 12 h when the treasury holds less than 0.01 ETH; the ETH per 12 h stays capped"},{"citation":"resolved","description":"Settles my earlier info note 692e75dc. The pot is ETH and the cost is IMD; no contract here has a rate, the brief names no ETH figure and forbids an owner who could set one. judgePrice() now reads the Intake's live price so a moved price needs no redeploy. The README states the economics (judging pays less than it costs until the pot is above about 17 x the IMD price in ETH; the swarm's heartbeat is expected to judge then). The requester may name a fixed ETH reimbursement; not a code defect.","line":270,"path":"src/MeatbagGame.sol","reproduction":"Pot 0.001 ETH (one entry). Next day the keeper calls judge(): the keeper's IMD balance falls by judgePrice() and claimable[keeper] == 3e13 wei; nothing else is credited.","severity":"info","snippet":"        uint256 reward = pot * KEEPER_BPS / 10_000;","title":"Settled (accepted as design): judge() credits only 3% of the pot and does not reimburse the IMD price"},{"citation":"resolved","description":"Settles my earlier info note eda126e1 and audit_economics' medium. It follows the brief's parameters (one entry per wallet, 40 slots at k x 0.001 ETH); any limit beyond the wallet needs identity the chain does not have. The README documents the break-even (about 0.24 ETH pot). Not a code defect.","line":209,"path":"src/MeatbagGame.sol","reproduction":"Pot 10 ETH. 40 fresh wallets each enter with nextSlotPrice() (0.82 ETH total); nextSlotPrice() == 0 and enter() reverts RoundFull for everyone else. Next day judge() plus a valid attestation naming any index 0..39 credits a funder wallet about 8.4 ETH.","severity":"info","snippet":"        if (hasEntered[day][msg.sender]) revert AlreadyEntered();","title":"Settled (design property): one funder can take all 40 slots of a round for 0.82 ETH and is certain to win it"},{"citation":"resolved","description":"Settles my earlier info note bc795037. The brief requires every heartbeat job to end with a Message on what it built, so free text from 0xd01122bBfFd00fc96252c8b29867a5359a3bca13 is required; 'no caller-supplied text' can only mean no public caller. The README and launch.json notes state the assumption. Not a code defect.","line":63,"path":"src/MeatbagHerald.sol","reproduction":"vm.prank(0xd01122bBfFd00fc96252c8b29867a5359a3bca13); herald.post('migrate at 0x1234') emits Message(TO, ...) and count increments; any other sender reverts NotAuthorised.","severity":"info","snippet":"    function post(string calldata text) external {","title":"Settled (trust assumption): the swarm wallet can post any text on the only official channel and nothing can revoke it"}],"hash":"973859d0fca1fbcae6b0e6c4312a145665c9c55eb955d78e0e51eb5c9d573087","nodeId":"d324c57d-6451-4349-ad02-f176c0dddef3","outcome":"completed","summary":"The findings file is written, the scratch tests are removed, and no repository file was changed.\n\n**Outcome: the one blocking finding is fixed, and nothing new was found.** The review is recorded in `.imd-findings.json` with six info entries that settle my earlier report and a coverage row for all 24 entry points.\n\n**The medium finding (first verdict out of gas) is confirmed fixed.** The oracle callback no longer calls the herald. A new internal function posts the first-verdict letter once, from the next judge, hung-jury declaration, sunset, claim or the public announce call. I traced every path that moves the round cursor: the public ones post the letter first, and the callback can only move it past a round that judge() already made Pending, so the letter cannot be skipped. The attached proof now passes on this tree, and all 129 non-fork tests pass. My own binary search of the callback gas limit:\n\n| Delivery case | Minimum gas now | Before |\n| --- | --- | --- |\n| First verdict, keeper already claimed | 171,477 | 206,156 |\n| First hung jury in the callback | 150,574 | about 150,500 |\n| Seventh hung jury in the callback | 87,349 | 291,874 |\n| Verdict after six hung rounds | 157,177 | not measured |\n\n**The four disputed advisories are settled as accepted.** The author's answers hold, so none opens a revision:\n\n- **Hung-jury race with judge()** stays reproducible, but any finite deadline for an unjudged round overlaps the last moment it could be judged, and the deadline is what keeps the sunset reachable when the Intake refuses requests. Downgraded from low to a recorded design choice.\n- **Heartbeat runs closer than 12 hours apart** when the treasury holds dust. The ETH per 12 hours stays capped and only goes to the swarm wallet. Documented in the README and manifest notes.\n- **Judge not reimbursed for the IMD price**, only 3% of the pot. No on-chain rate exists and no owner may set one. Documented economics.\n- **Sybil capture of a round** and **the swarm wallet's free text** follow the brief's own parameters and requirements. Documented as a design property and a trust assumption.\n\nI also checked that the manifest added in later commits matches the hook: constructor arguments are the pool manager and factory in declaration order, the five permissions match getHookPermissions, and the pool fee is the policy's 12500. The mainnet fork tests were not run because they need an RPC endpoint; everything else was verified offline.","treeHash":null,"usage":{"cachedInputTokens":1300483,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":14960,"runtime":"claude","turns":15,"wallClockMs":280585}},{"artifacts":[],"attempt":1,"bundleHash":"b268703777d000e879ab0d625a534709566b7083afd1c8fe055e749f3965e122","device":"c722c2e9ac9aa084","findings":[{"description":"For the two swap shapes where ETH is the specified currency (exact-input buy, exact-output sell) `beforeSwap` computes the fee from `params.amountSpecified` and returns it as the hook's specified delta. The PoolManager charges that delta whatever the pool actually fills. A swapper whose `sqrtPriceLimitX96` stops the swap early (any router or aggregator that passes a real limit, or a user protecting against a sandwich) pays the full fee on ETH that never traded, and `volume` records ETH that never moved, so the herald's 1/10/100 ETH milestone letters can be triggered by swaps that moved almost nothing. The brief asks for the fee 'from the settled ETH delta'. In the launch window the effect is 25% of the requested amount: a 100 ETH order filled for 3 ETH is charged 25 ETH. On an exact-output sell the fee can exceed what the seller receives.","line":224,"path":"src/MeatbagHook.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} from \"v4-core/src/types/Currency.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {MeatbagHook} from \"src/MeatbagHook.sol\";\nimport {MeatbagToken} from \"src/MeatbagToken.sol\";\nimport {MeatbagGame} from \"src/MeatbagGame.sol\";\nimport {HeartbeatTreasury} from \"src/HeartbeatTreasury.sol\";\n\n/// @title Proof: a swap that stops at its price limit is charged on the requested amount, not the ETH that moved\n/// @notice When ETH is the specified currency (exact-input buy, exact-output sell) the hook computes its\n/// fee in `beforeSwap` from `amountSpecified` and keeps it whatever the pool fills. A swapper whose\n/// `sqrtPriceLimitX96` stops the swap early pays the full fee on ETH that never traded, and `volume`\n/// records ETH that never moved. Both cases below pass once the hook either charges on the settled ETH\n/// delta or refuses the partially filled swap.\ncontract PartialFillProofTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    address constant SWARM = 0xd01122bBfFd00fc96252c8b29867a5359a3bca13;\n\n    PoolManager manager;\n    MeatbagToken token;\n    MeatbagHook hook;\n    MeatbagGame game;\n    HeartbeatTreasury treasury;\n    PoolSwapTest swapRouter;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        token = new MeatbagToken();\n        bytes memory creationCode =\n            abi.encodePacked(type(MeatbagHook).creationCode, abi.encode(address(manager), address(this)));\n        bytes32 initCodeHash = keccak256(creationCode);\n        for (uint256 i = 0; i < 500_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            address at;\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            require(at == predicted, \"hook deployment reverted\");\n            hook = MeatbagHook(payable(at));\n            break;\n        }\n        require(address(hook) != address(0), \"no salt\");\n        game = hook.game();\n        treasury = hook.treasury();\n        swapRouter = new PoolSwapTest(manager);\n        PoolModifyLiquidityTest lpRouter = new PoolModifyLiquidityTest(manager);\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12500, 60, IHooks(address(hook)));\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.deal(address(this), 1_000_000 ether);\n        token.approve(address(lpRouter), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n        lpRouter.modifyLiquidity{value: 2_000 ether}(\n            key, ModifyLiquidityParams(-887220, 887220, 1_000 ether, bytes32(0)), \"\"\n        );\n        vm.warp(block.timestamp + 1 hours); // past the decay: the rate is 2%\n    }\n\n    function feesHeld() internal view returns (uint256) {\n        return game.pot() + SWARM.balance + address(treasury).balance + hook.owedPot() + hook.owedSwarm()\n            + hook.owedTreasury();\n    }\n\n    function test_exactInputBuyStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() public {\n        uint256 ethBefore = address(this).balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60); // stops after roughly 3 ETH of a 100 ETH order\n        try swapRouter.swap{value: 100 ether}(\n            key, SwapParams(true, -int256(100 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 paid = ethBefore - address(this).balance;\n            uint256 fee = feesHeld();\n            uint256 poolTook = paid - fee;\n            assertLt(poolTook, 10 ether, \"the limit should have stopped the swap early\");\n            // Expected: 2% of the ETH that actually traded (either side of it, within rounding).\n            assertLe(fee, paid * 200 / 10_000 + 1, \"fee exceeds 2% of the ETH the swapper paid\");\n            assertGe(fee, poolTook * 200 / 10_000 - 1, \"fee below 2% of the ETH the pool took\");\n            assertLe(hook.volume(), paid, \"volume counts ETH that never moved\");\n        } catch {\n            // A hook that refuses partially filled swaps also satisfies the brief.\n        }\n    }\n\n    function test_exactOutputSellStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() public {\n        uint256 ethBefore = address(this).balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(60);\n        try swapRouter.swap(\n            key, SwapParams(false, int256(100 ether), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 received = address(this).balance - ethBefore;\n            uint256 fee = feesHeld();\n            uint256 poolPaid = received + fee;\n            assertLt(poolPaid, 10 ether, \"the limit should have stopped the swap early\");\n            assertLe(fee, poolPaid * 200 / 10_000 + 1, \"fee exceeds 2% of the ETH that left the pool\");\n            assertLe(hook.volume(), poolPaid, \"volume counts ETH that never moved\");\n        } catch {\n            // A hook that refuses partially filled swaps also satisfies the brief.\n        }\n    }\n}","reproduction":"Fresh PoolManager, full-range liquidity 1000e18 at price 1:1, 1 hour after launch (rate 2%). (1) buy exact-in 100 ETH with sqrtPriceLimitX96 = getSqrtPriceAtTick(-60): the pool takes 3.042 ETH, the swapper pays 5.042 ETH, the hook keeps 2.000 ETH (expected 2% of ~3.04 ETH = ~0.061 ETH), hook.volume() = 100 ETH (expected ~3.04 ETH). (2) sell exact-out 100 ETH with limit getSqrtPriceAtTick(60): the pool pays 2.995 ETH, the seller receives 0.955 ETH, the hook keeps 2.041 ETH (expected ~0.06 ETH). Fix options: in afterSwap compare the settled ETH delta with the amount the fee was computed on and revert on a partial fill (PartialFillNotSupported), or recompute the fee from the settled delta and return the difference; either makes the proof pass.","severity":"high","title":"Fee charged on the requested amount, not the settled ETH, when a swap stops at its price limit"},{"description":"`_hung` calls `_sunset` when the streak reaches 7. Inside `onOracleResult` that path needs about 329k gas (measured with 3 entrants per round; the first hung jury alone needs about 187k because of the herald letter), so the writer's 200,000 gas call reverts, the attestation is recorded as not delivered and not retried, and the round stays Pending for VERDICT_TIMEOUT (25 h) until someone calls `declareHungJury()`. The README documents the fallback; the suite's test_seventhHungJuryFromTheOracleStillSunsetsThePot proves the pot still reaches the entrants, so this is a delay rather than a loss. A fix is to mark the round hung in the callback and defer the sunset loop to a separate public call (or to the next judge()).","line":399,"path":"src/MeatbagGame.sol","reproduction":"Fund the pot with 7 ETH, run six rounds hung by timeout, judge a seventh and deliver a panel attestation with agreed=3 through a 200,000-gas call: the call returns false, round status stays Pending, unsettledStreak stays 6. With unbounded gas the same call succeeds and the sunset fires.","severity":"low","title":"Seventh hung jury delivered by the oracle cannot run the sunset within the 200k callback stipend"},{"description":"The brief says the judge() caller 'is repaid from the pot plus 3% of it'. The implementation credits 3% of the pot (in ETH) at request time and nothing else; the 0.5 IMD is not repaid. The README records this as a deliberate reading (no price feed for IMD in ETH). If the requester meant repayment, the design needs an ETH-denominated reimbursement (fixed or owner-less oracle-priced) or the keeper economics stay negative while the pot is small: 3% of a 0.01 ETH pot does not cover 0.5 IMD. Reported, not asserted either way.","line":233,"path":"src/MeatbagGame.sol","reproduction":"Pot 0.001 ETH (one entry), keeper calls judge(): keeper spends 0.5 IMD and receives a 0.00003 ETH claim.","severity":"low","title":"Keeper is paid 3% of the pot but not repaid the 0.5 IMD the brief says comes 'from the pot'"},{"description":"`_sunset` splits the whole pot among the entrants of the seven hung rounds. The pot at that moment also holds the entry fees of rounds that closed after the window (awaiting judge()) and of today's open round, so those entrants fund the sunset and then play for a pot of dust. This is a consequence of a single pot and is arguably by design; it is noted so the requester can decide whether later rounds' entry fees should be excluded.","line":409,"path":"src/MeatbagGame.sol","reproduction":"Seven rounds hung, an eighth round with 40 entries (0.82 ETH) closed but not yet judged: the seventh declareHungJury() hands the 0.82 ETH to the earlier entrants.","severity":"info","title":"A sunset distributes ETH that entrants of later, still-unjudged rounds paid in"},{"description":"An exact-input buy pays 2% of the ETH the buyer sends (the pool gets 98%); an exact-output buy pays 2% of what the pool took (1.96% of the buyer's spend). Sells mirror it (2% of pool out vs 2.04% of what the seller receives). The README documents this. The 4 bps gap is small but means the same trade costs a different fee depending on how it is expressed.","line":261,"path":"src/MeatbagHook.sol","reproduction":"After the decay: buyExactIn(1 ether) fee = 0.02 ETH; buyExactOut for the same tokens costs about 0.0196 ETH in fee.","severity":"info","title":"Exact-in buys and exact-out buys use different fee bases (2% of ETH in vs 2% of ETH the pool took)"}],"hash":"a297e5ffdb8b7d446eaa1171ed4506a728812247f5618975aca7924b7bf66399","nodeId":"410fe7f3-5cae-4cfd-b1bd-fb69ed44ee5b","outcome":"completed","summary":"Done. The test suite now has 103 passing tests in the default profile (up from 60), 5 passing fork tests against the real mainnet PoolManager, and one high-severity defect reported with a runnable proof.\n\n**What I added** (all under `test/`, committed as 821cfbe):\n\n- **Invariant suites** in `test/invariant/`, each with a handler driving random bounded call sequences and inline forge-config run counts:\n  - Game: balance equals pot plus unpulled claims, ETH in equals ETH held plus ETH out, every pull balance and unclaimed sunset share sums to `totalClaimable`, rounds finish in order and never reopen, the hung streak never reaches seven, pot and record bounded by deposits.\n  - Hook, on an ETH-seeded pool and a tokens-only pool: every reported fee is in the pot, the swarm wallet, the treasury, owed, or a claim; splits are exactly 55/25/20 of the 2% base with the launch surplus to the pot; claims match the manager's ERC-6909 balance; the hook never keeps ETH or MEAT; rate bounds and decay end; volume and herald milestones agree. Each swap is also checked in the handler against the ETH that actually moved.\n  - Treasury: ETH leaves only through runs, never more than 0.01 ETH per 12 hours over any elapsed span, from any caller.\n- **Hook edge cases**: dust swaps with a zero fee, one-wei exact-output sells, fuzzed split exactness, monotone bounded decay, the 30-minute boundary, a swarm wallet that rejects ETH (swaps continue, share stays owed, retried by anyone), claims that accumulate while the manager is dry, redeem reverting until it can pay, wrong-pool refusal, unused callbacks reverting even for the manager.\n- **Game edge cases**: exact day boundary, inclusive ASCII bounds, under and zero payment, fuzzed triangle pricing, keeper without IMD or approval, exact 3% reward, wrong answer type, malformed answer bytes leaving the round pending then recoverable, a signature from the Robinhood Chain domain, expiry at the exact second, truncated and empty signatures, a reentrant winner, stranger sunset claims, per-entry sunset shares, an eighth hung round, a 40-entry body under the 16 KiB limit settling under 200k gas, and compounding carry-over.\n- **Fork tests** in `test/fork/MainnetForkFees.t.sol`: exact splits after the decay, launch surplus to the pot, and a stranger's swap, all on the real PoolManager. They ran green here with the fork profile.\n\n**Findings** are in `.imd-findings.json` (the harness keeps it out of git):\n\n- **High, with proof**: when ETH is the specified currency, the fee is computed on the requested amount in `beforeSwap`, so a swap that stops at its price limit is charged on ETH that never traded. A 100 ETH exact-input buy that filled 3.04 ETH paid a 2 ETH fee, and a limited exact-output sell paid a 2.04 ETH fee on 0.95 ETH received. Volume is also inflated, which can trigger milestone letters falsely. The proof in `test/scratch/PartialFillProof.t.sol` fails on the current code and passes if the hook either charges on the settled delta or refuses partial fills.\n- **Low**: a seventh hung jury delivered by the oracle needs about 329k gas for the sunset, over the 200k stipend, so the pot is delayed 25 hours until `declareHungJury()`. My test covers the recovery path without asserting the failed delivery is correct.\n- **Low**: the keeper gets 3% of the pot but is not repaid the 0.5 IMD, which the brief's wording may have intended.\n- **Info**: a sunset also hands out entry fees from later unjudged rounds, and exact-in versus exact-out buys use slightly different fee bases.\n\n**Two things to know**: I did not test around the partial-fill defect, so the delivered suite contains no assertion blessing it. Writing the tests also surfaced a via-IR gotcha where `block.timestamp` and `block.chainid` reads can be reordered past cheatcodes, so my tests use `vm.getBlockTimestamp()` and explicit chain ids.","treeHash":"616ffc61f003484b1c6f694be619d02812893edc","usage":{"cachedInputTokens":4751006,"inputTokens":962,"model":"claude-fable-5-1","outputTokens":84546,"runtime":"claude","turns":54,"wallClockMs":1728021}},{"artifacts":[],"attempt":1,"bundleHash":"f93ed62438a9792d7f390e7bfe8ec46540738d619ba69f74ccb59070387bc0e4","device":"99c6d0bcc495ad61","findings":[],"hash":"a7a05c784f3171358e708f485269442964248b7fcb7d527bfbf242a7db08b885","nodeId":"88a67906-0f6f-48f4-a43a-ca6e6f023edd","outcome":"completed","summary":"The manifest at `launch.json` is verified against the accepted implementation and the protected floor, with two precision fixes to its notes. Only that file changed.\n\n**What I checked**\n\n- `MeatbagHook` takes exactly two constructor arguments, the pool manager and the factory, written as `$poolManager` and `$factory`. There is no owner, matching the brief's no-admin rule, and the token is read from the PoolKey in beforeInitialize rather than passed in.\n- The declared permissions match what getHookPermissions returns: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. The address flags come to 0x20CC.\n- Token is `MeatbagToken`, name MEATBAG, symbol MEAT, 18 decimals, no constructor arguments.\n- Pool fields are the launch's own: native ETH pair, fee 12500, tickSpacing 60, the one-to-one initial price.\n- Every address in the file is lowercase, the first key is `\"kind\": \"univ4_hook\"`, and the notes are 3570 characters.\n\n**Test results**\n\n- `forge build` succeeds and the default suite passes, 72 tests, 0 failures.\n- The protected hook test, run from a scratch copy with the manifest's flags, fee, tick spacing, price and probe addresses, passes all 4 checks, including initialization from the factory probe on the launch pool.\n- The protected token test passes 6 checks, with the policy-supply check skipped because that value is not supplied by the manifest.\n\n**What I changed in the notes**\n\n- The heartbeat description now states the actual rule in the source: fundNextRun sends min(balance, 0.01 ETH) and closes the treasury for a proportional share of 12 hours, so the cap is 0.01 ETH per 12 hours rather than literally one call per 12 hours.\n- The size figures now match the compiled artifacts: about 26.5 KB of init code and about 7.3 KB of runtime.\n\nNo findings were listed in this revision brief, so I left the hook, token, pool and constructor sections as they were, since they already describe the accepted code correctly.","treeHash":"78937ab39638844556a0c24b6925af62c337d1f7","usage":{"cachedInputTokens":714652,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":8173,"runtime":"claude","turns":14,"wallClockMs":311908}},{"artifacts":[],"attempt":1,"bundleHash":"ce4ad27da99b9e6a6abf17dd7e8816d3c19808445a5ff911423d72468620a2d7","device":"f9a12c57a094ca8d","findings":[{"description":"onOracleResult runs under the Intake writer's 200,000 gas stipend. The settled path pays for the EIP-712 signature check, the round's storage, cursor and streak, pot and prize, two claim slots (claimable[winner], totalClaimable) and the herald's first-verdict letter (a cold external call with two more storage writes). Measured with forge: 194,464 gas when totalClaimable is still non-zero (5,500 gas of headroom), 211,564 gas when totalClaimable and the winner's claimable slot are both zero, which is the state after the keeper pulled their 3% reward with claim() before the oracle answered. A keeper bot that claims at once puts the game in that state on every round, so every first verdict runs out of gas, the callback reverts, the request times out, the round is declared hung and the pot carries over. The herald's FIRST_VERDICT flag never flips, so nothing gets cheaper for the next round; after seven such rounds the sunset rule returns the pot. Later verdicts (after a first one somehow landed) need about 181,000 and fit. Fix options: drop the herald call from the callback (post the first-verdict letter from claim() or a separate keeper call), or keep the callback to the bare settlement and move the prize credit to a pull with lazily initialised slots, and add a test that measures the worst-case first verdict with totalClaimable == 0 under exactly 200,000 gas. The invariant handler in test/invariant/GameInvariant.t.sol records every such failure in stipendFailures and repeats the delivery with unbounded gas so the rest of the suite can run; it does not assert the failure is correct.","line":338,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev A stand-in IMD token.\ncontract ProofImd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1_000 ether);\n    }\n}\n\n/// @dev A stand-in Intake that records the request and calls the callback the way the oracle writer\n/// does: from its own address, with exactly the 200,000 gas stipend.\ncontract ProofIntake is IIntake {\n    uint256 public count;\n    bytes public lastBody;\n    address public target;\n    bytes4 public selector;\n\n    function priceOf(bytes32, address) external pure returns (uint256) {\n        return 0.5 ether;\n    }\n\n    function request(bytes32, bytes calldata body, Callback calldata callback, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        requestId = keccak256(abi.encode(\"intake\", ++count));\n        lastBody = body;\n        target = callback.target;\n        selector = callback.selector;\n    }\n\n    function deliver(bytes32 requestId, OracleAttestation.Attestation memory a, bytes memory signature)\n        external\n        returns (bool ok)\n    {\n        (ok,) = target.call{gas: 200_000}(abi.encodeWithSelector(selector, requestId, a, signature));\n    }\n}\n\n/// @title The first verdict does not fit the oracle writer's 200,000 gas stipend once the keeper has\n/// pulled their reward\n/// @notice Sequence: one entrant, the day closes, a keeper calls `judge()` (credited 3% of the pot),\n/// the keeper calls `claim()` (which zeroes `totalClaimable` and their `claimable` slot), and the oracle\n/// delivers a well-formed, well-signed verdict under 200,000 gas. Expected: the round settles and the\n/// winner is credited 80% of the pot. Actual: `onOracleResult` runs out of gas (it needs about 211,000:\n/// the signature check, the round's storage, two fresh `claimable`/`totalClaimable` slots, and the\n/// herald's first-verdict letter, a cold external call with two more storage writes), the callback\n/// reverts, the request times out and the round is declared hung. A keeper bot that claims at once\n/// makes every first verdict fail the same way, so the game can never settle a round.\ncontract FirstVerdictStipendProofTest is Test {\n    uint256 constant SIGNER_KEY = 0xA11CE;\n\n    ProofIntake intake;\n    ProofImd imd;\n    MeatbagHerald herald;\n    MeatbagGame game;\n    address entrant = address(0xA005);\n    address keeper = address(0xA004);\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        intake = new ProofIntake();\n        imd = new ProofImd();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), vm.addr(SIGNER_KEY));\n        imd.transfer(keeper, 10 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(entrant, 1 ether);\n    }\n\n    function test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() public {\n        vm.prank(entrant);\n        game.enter{value: 0.001 ether}(\"hello, i am a person\");\n        vm.warp(vm.getBlockTimestamp() + 30 hours);\n        vm.prank(keeper);\n        bytes32 id = game.judge();\n        // The keeper pulls their 3% at once, as a bot would. Nothing is owed to anyone any more.\n        vm.prank(keeper);\n        game.claim();\n        assertEq(game.totalClaimable(), 0);\n\n        OracleAttestation.Attestation memory a = OracleAttestation.Attestation({\n            requestId: keccak256(abi.encode(\"oracle\", id)),\n            chainId: 1,\n            questionHash: keccak256(intake.lastBody()),\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(uint256(0)),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: keccak256(abi.encode(\"panel\", id)),\n            panelSize: 7,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(vm.getBlockTimestamp()),\n            expiresAt: uint64(vm.getBlockTimestamp() + 1 days)\n        });\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, game.attestationDigest(a));\n        bool ok = intake.deliver(id, a, abi.encodePacked(r, s, v));\n\n        assertTrue(ok, \"a valid first verdict must land under the writer's 200,000 gas stipend\");\n        MeatbagGame.Round memory round = game.round(game.roundDays(0));\n        assertTrue(round.status == MeatbagGame.Status.Settled, \"the round did not settle\");\n        assertGt(game.claimable(entrant), 0, \"the winner was not paid\");\n    }\n}","reproduction":"One entrant enters (0.001 ETH). Warp 30 hours. Keeper calls judge() and is credited 3% of the pot. Keeper calls claim() (totalClaimable becomes 0). The intake delivers a well-formed verdict (panelSize 7, quorum 4, agreed 5, answer index 0, signed by the oracle signer for this consumer) with call{gas: 200_000}. Expected: the call succeeds, the round is Settled and the entrant has 80% of the pot claimable. Actual: the call returns false (out of gas at about 211,000 needed), the round stays Pending, and is later declared Hung.","severity":"high","title":"The first verdict does not fit the oracle writer's 200,000 gas stipend once the keeper has pulled their reward"},{"description":"hungJuryAt() for a closed round that nobody judged is (day + 1) * 1 days + VERDICT_TIMEOUT, i.e. 01:00 UTC two days after the round's day, and declareHungJury() needs no other condition. judge() stays callable on the same round at that moment. So whenever a round reaches that time unjudged (keepers were slow, or the previous round's request was pending until then, which the cursor's one-round-at-a-time rule makes routine when the oracle takes most of its 25 hours), anyone can hang it in the same block a keeper would have judged it, by ordering or front-running. An entrant who prefers a sunset split to a panel, or anyone who wants no verdicts, can keep doing this: after seven rounds the pot is split among the streak's entrants. Funds are not lost (carry-over, then sunset) and the swarm's 12-hour judging cadence normally judges well before 01:00 two days later, so this is a liveness and incentive issue, not a loss. Possible fixes: only allow hanging an unjudged round when judge() cannot work (track a failed request attempt, or require a longer timeout than the judge window), or let judge() take priority by making declareHungJury() on an Open round require that nobody has judged for one full extra day.","line":286,"path":"src/MeatbagGame.sol","reproduction":"Day D: alice enters. Warp to (D + 2) * 1 days + 1 hours (hungJuryAt for the round). judge() would succeed here (the round is closed, the intake accepts). A stranger calls declareHungJury() first: the round is Hung, unsettledStreak is 1, and a keeper's judge() in the same block reverts NothingToJudge (or moves to a later round). Expected by the brief: 'No verdict = hung jury'; a round that could still be judged was never put to the panel.","severity":"low","title":"declareHungJury() can pre-empt judge() on a round that is still judgeable"},{"description":"The brief says fundNextRun() is callable 'at most once per 12 h'. The revised contract closes for INTERVAL * amount / CAP_PER_RUN, so a run that found less than 0.01 ETH reopens sooner, and an amount under CAP_PER_RUN / 43200 (about 2.3e11 wei) closes for zero seconds and may be followed by another call in the same block. The economic cap still holds (the invariant suite checks that paid-out ETH never exceeds 0.01 ETH per 12 hours plus one run plus one second's worth of cap per run for the rounding), the money only ever goes to the swarm's wallet, and the change deliberately stops a dust call from burning a heartbeat's slot. Recorded because it is a literal departure from the brief that the requester may want to confirm; the tests cover the behaviour as implemented.","line":41,"path":"src/HeartbeatTreasury.sol","reproduction":"Fund the treasury with 1 wei; call fundNextRun() (sends 1 wei, nextRunAt == block.timestamp); fund 1 wei again; call fundNextRun() in the same block: it succeeds. With the brief's literal rule the second call would revert TooSoon.","severity":"info","title":"HeartbeatTreasury allows more than one fundNextRun() per 12 hours when the treasury is below the cap"},{"description":"The pot is one balance. When the seventh unsettled round is hung and the split runs (in judge(), declareHungJury() or sunset()), share = pot / entrants over the streak's rounds. Entry fees already paid into the pot by entrants of the current open round, and trading fees that arrived after the seventh round closed, go to the previous rounds' entrants, and the open round continues with an empty pot until trading refills it. This follows the brief ('those entrants get equal pull claims' of the pot) and the single-pot design, and no ETH is lost, but the open round's entrants are paying into a pot they cannot win. Recorded as a design property for the requester, not asserted either way by the tests.","line":457,"path":"src/MeatbagGame.sol","reproduction":"Seven rounds hung in a row (pot P). On day 8 bob enters (0.001 ETH into the pot). Anyone calls sunset(): the seven rounds' entrants split P + 0.001 ETH; bob's round starts with pot 0.","severity":"info","title":"A sunset splits the whole pot, including today's open round's entry fees and any trading fees received meanwhile, among the streak's entrants only"}],"hash":"a9e09452763e2aa9d25709443b2fbb287c5c317d5f19b3726a63bcf553d8d737","nodeId":"410fe7f3-5cae-4cfd-b1bd-fb69ed44ee5b","outcome":"completed","summary":"Done. The suite passes in the verifier's configuration, and one high-severity defect is reported with a runnable proof.\n\n**What was wrong when I started.** The implementation was revised after my previous round, and five of my tests encoded the old behaviour: the hook invariant suites (exact-output buys now pay the buy rate on the whole spend), the treasury invariants (closing time is now proportional to the amount sent), and two edge tests (the oracle callback no longer runs the sunset itself).\n\n**What I changed, all under `test/`:**\n- `test/invariant/HookInvariant.t.sol`: new fee formula for exact-output buys, plus handler actions for price-limited swaps in all four modes (refused `PartialFill` swaps must leave no trace, the other two pay on settled ETH) and ETH donated straight to the hook. Conservation and split invariants now account for donations prepaying owed shares while claims are pending.\n- `test/invariant/TreasuryInvariant.t.sol`: dust feeding, and the cap restated as an exact bound: paid-out ETH never exceeds 0.01 ETH per 12 hours plus one run, with one second's worth of cap per run for the rounding; full runs still at most one per 12 hours.\n- `test/invariant/GameInvariant.t.sol`: handler now exercises `sunset()`, hung declarations on unjudged rounds at `hungJuryAt()`, a moving intake price, and a refusing intake. The streak invariant allows exactly seven with `sunsetDue()` true.\n- Edge tests added: fuzzed exact-output buy rate across the decay, refused partial fills leaving no state, donations during pending claims, `hungJuryAt` boundaries, `sunset()` refusal and single settlement, live-price pulls.\n- `test/fork/MainnetForkFees.t.sol`: `judge()` against the real mainnet Intake. It passed on a fork: price 0.5 IMD, request accepted, approval consumed.\n\n**Findings in `.imd-findings.json`:**\n- **High, with proof:** the first verdict callback needs about 211,000 gas once the keeper has pulled their 3% reward, over the writer's 200,000 stipend. A keeper bot that claims promptly makes every first verdict fail, so no round can ever settle. The proof test fails on the current code. The game invariant handler counts this as `stipendFailures` and re-delivers with unbounded gas rather than asserting it correct.\n- **Low:** `declareHungJury()` can pre-empt a still-judgeable round once `hungJuryAt()` passes.\n- **Info:** the treasury departs from the literal \"at most once per 12 h\" when below the cap, and a sunset splits the current open round's entry fees to the previous streak's entrants.\n\nResults: 121 tests pass without scratch and fork files, 6 fork tests pass against mainnet, build clean.","treeHash":"c3708cbe1a41eea988b740721dae5532b350e060","usage":{"cachedInputTokens":2964695,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":77437,"runtime":"claude","turns":62,"wallClockMs":1359832}},{"artifacts":[],"attempt":1,"bundleHash":"06565a12bcd95e33dabed9f172ac0edf429da0cb7462fa436719408f2723b302","device":"c9668a6c89d33b13","findings":[],"hash":"ac59c2525ca2024be729694128c9f05e125d932ce3f13f6c402b921bd801894c","nodeId":"88a67906-0f6f-48f4-a43a-ca6e6f023edd","outcome":"completed","summary":"Updated only [launch.json](/root/.identitymd/work/c7edbc2b-cefa-40b3-981a-1a332ba4b263/88a67906-0f6f-48f4-a43a-ca6e6f023edd/launch.json), correcting notes and documenting existing heartbeat, reimbursement and herald-text gaps.\n\nVerified constructor arguments, permissions, pool settings and the notes limit.\n\nChecks passed: `forge build`, 75 local tests and 2 mainnet-fork tests.","treeHash":"5a85572714082f93202b145aac8339b76e553f7a","usage":{"cachedInputTokens":594176,"inputTokens":85174,"model":"gpt-6-astra","outputTokens":6953,"runtime":"codex","turns":4,"wallClockMs":226989}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"723b11f958c65250","findings":[{"citation":"resolved","description":"Reported by write_foundry_tests (high); the invariant handler in test/invariant/GameInvariant.t.sol already counts this as stipendFailures. The oracle-consumer reference gives onOracleResult 200,000 gas and says a callback that runs out of gas is recorded as not delivered and is not retried. The settled path pays for the EIP-712 check, consumed[], the round's slots, cursor, unsettledStreak, pot, prize, claimable[winner], totalClaimable, the Verdict event and the herald's first-verdict letter (cold call, sent[] and count writes, a long string event). I measured the minimum gas that lands the callback by binary search (test/scratch/GasProbe.t.sol): 206,156 for the game's first round when totalClaimable is zero (the keeper claimed their 3% before the oracle answered; cursor 0->1 and totalClaimable 0->x are both 22,100-gas writes), 189,056 when the keeper has not claimed, 191,856 for the first verdict after an earlier round was hung, 125,807 for later verdicts. So the failing state is narrower than the specialist's 'every first verdict': it is the game's first-ever round, when the keeper (the swarm's heartbeat job, which judges and may claim in the same run) claims before delivery. In that state the valid verdict is dropped, the entrant the panel chose is never paid, the keeper's 0.5 IMD is spent for nothing, and the round is declared hung after VERDICT_TIMEOUT; the pot carries over and later rounds fit (with about 8,000 gas of headroom for the first verdict). README line 130 says 'The callback runs within the 200,000 gas stipend (tested)', but test/MeatbagGame.t.sol line 593 measures it only with totalClaimable still non-zero. Fix: move the herald's first-verdict letter out of the callback (post it from claim() or from the next judge()/sunset()/a public call when round.status == Settled and sent[FIRST_VERDICT] is false), and add a test that delivers the first verdict with totalClaimable == 0 under exactly 200,000 gas; that alone brings the first round to about 170,000 gas.","line":347,"path":"src/MeatbagGame.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {MeatbagGame, IIntake} from \"src/MeatbagGame.sol\";\nimport {MeatbagHerald} from \"src/MeatbagHerald.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev A stand-in IMD token.\ncontract ProofImd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1_000 ether);\n    }\n}\n\n/// @dev A stand-in Intake that records the request and calls the callback the way the oracle writer\n/// does: from its own address, with exactly the 200,000 gas stipend.\ncontract ProofIntake is IIntake {\n    uint256 public count;\n    bytes public lastBody;\n    address public target;\n    bytes4 public selector;\n\n    function priceOf(bytes32, address) external pure returns (uint256) {\n        return 0.5 ether;\n    }\n\n    function request(bytes32, bytes calldata body, Callback calldata callback, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        requestId = keccak256(abi.encode(\"intake\", ++count));\n        lastBody = body;\n        target = callback.target;\n        selector = callback.selector;\n    }\n\n    function deliver(bytes32 requestId, OracleAttestation.Attestation memory a, bytes memory signature)\n        external\n        returns (bool ok)\n    {\n        (ok,) = target.call{gas: 200_000}(abi.encodeWithSelector(selector, requestId, a, signature));\n    }\n}\n\n/// @title The first verdict does not fit the oracle writer's 200,000 gas stipend once the keeper has\n/// pulled their reward\n/// @notice Sequence: one entrant, the day closes, a keeper calls `judge()` (credited 3% of the pot),\n/// the keeper calls `claim()` (which zeroes `totalClaimable` and their `claimable` slot), and the oracle\n/// delivers a well-formed, well-signed verdict under 200,000 gas. Expected: the round settles and the\n/// winner is credited 80% of the pot. Actual: `onOracleResult` runs out of gas (it needs about 211,000:\n/// the signature check, the round's storage, two fresh `claimable`/`totalClaimable` slots, and the\n/// herald's first-verdict letter, a cold external call with two more storage writes), the callback\n/// reverts, the request times out and the round is declared hung. A keeper bot that claims at once\n/// makes every first verdict fail the same way, so the game can never settle a round.\ncontract FirstVerdictStipendProofTest is Test {\n    uint256 constant SIGNER_KEY = 0xA11CE;\n\n    ProofIntake intake;\n    ProofImd imd;\n    MeatbagHerald herald;\n    MeatbagGame game;\n    address entrant = address(0xA005);\n    address keeper = address(0xA004);\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        intake = new ProofIntake();\n        imd = new ProofImd();\n        address predictedGame = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);\n        herald = new MeatbagHerald(predictedGame);\n        game = new MeatbagGame(herald, address(intake), address(imd), vm.addr(SIGNER_KEY));\n        imd.transfer(keeper, 10 ether);\n        vm.prank(keeper);\n        imd.approve(address(game), type(uint256).max);\n        vm.deal(entrant, 1 ether);\n    }\n\n    function test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() public {\n        vm.prank(entrant);\n        game.enter{value: 0.001 ether}(\"hello, i am a person\");\n        vm.warp(vm.getBlockTimestamp() + 30 hours);\n        vm.prank(keeper);\n        bytes32 id = game.judge();\n        // The keeper pulls their 3% at once, as a bot would. Nothing is owed to anyone any more.\n        vm.prank(keeper);\n        game.claim();\n        assertEq(game.totalClaimable(), 0);\n\n        OracleAttestation.Attestation memory a = OracleAttestation.Attestation({\n            requestId: keccak256(abi.encode(\"oracle\", id)),\n            chainId: 1,\n            questionHash: keccak256(intake.lastBody()),\n            answerType: OracleAttestation.ANSWER_UINT256,\n            answer: abi.encode(uint256(0)),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: keccak256(abi.encode(\"panel\", id)),\n            panelSize: 7,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(vm.getBlockTimestamp()),\n            expiresAt: uint64(vm.getBlockTimestamp() + 1 days)\n        });\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, game.attestationDigest(a));\n        bool ok = intake.deliver(id, a, abi.encodePacked(r, s, v));\n\n        assertTrue(ok, \"a valid first verdict must land under the writer's 200,000 gas stipend\");\n        MeatbagGame.Round memory round = game.round(game.roundDays(0));\n        assertTrue(round.status == MeatbagGame.Status.Settled, \"the round did not settle\");\n        assertGt(game.claimable(entrant), 0, \"the winner was not paid\");\n    }\n}","reproduction":"Fresh game (cursor 0). Day D: entrant enters for 0.001 ETH. Warp 30 hours. keeper calls judge() (credited 3e13 wei) then claim(), so totalClaimable == 0. The intake delivers a well-formed verdict (panelSize 7, quorum 4, agreed 5, answer index 0, signed by the game's oracle signer) with call{gas: 200_000}. Expected: true, round Settled, claimable[entrant] == 80% of the pot. Actual: the call returns false (out of gas; it first succeeds at 206,156 gas), the round stays Pending, and after VERDICT_TIMEOUT declareHungJury() hangs it. Without the claim() the same delivery lands at 189,056 gas. Proof: the attached test fails on this tree with 'a valid first verdict must land under the writer's 200,000 gas stipend'.","severity":"medium","snippet":"        emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n        herald.announce(_H_FIRST_VERDICT);","title":"The game's first verdict runs out of the oracle writer's 200,000 gas stipend when the keeper has pulled their reward before delivery, so the first round is lost to a hung jury"},{"citation":"resolved","description":"Introduced by the fix for the stranded-pot finding; reported by write_foundry_tests (low). hungJuryAt() for an Open round at the cursor is (day + 1) * 1 days + VERDICT_TIMEOUT, and declareHungJury() needs nothing else, while judge() stays valid on the same round. So once a closed round reaches that time unjudged (slow keepers, or the previous round's request pending until then, which the one-round-at-a-time cursor makes routine when the oracle uses most of its 24-hour window), anyone can hang it in the block a keeper would have judged it, by ordering or front-running; repeated for seven rounds the pot is split by the sunset rule without a panel ever reading the entries. No funds are lost and the swarm's 12-hour cadence normally judges well inside the 25-hour window, so this is liveness and incentive, not loss. Possible fixes: let declareHungJury() hang an Open round only after a longer deadline than the judge window (for example two full days after it closed), or only when a request attempt has failed.","line":290,"path":"src/MeatbagGame.sol","reproduction":"test/scratch/Preempt.t.sol::test_strangerHangsAJudgeableRound (passes, showing the behaviour): day D alice enters; warp to (D + 2) * 1 days + 1 hours (== hungJuryAt()). A snapshot shows keeper's judge() succeeds here. Instead address(0xBAD) calls declareHungJury() first: round D is Hung, unsettledStreak == 1, and the keeper's judge() in the same block reverts NothingToJudge. Expected per the brief: 'No verdict = hung jury'; this round was never put to the panel.","severity":"low","snippet":"        if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n        _hung(day, true);","title":"declareHungJury() can hang a round that judge() could still send to the panel, from 01:00 UTC two days after the round's day"},{"citation":"resolved","description":"The proportional closing that fixed the dust-call finding trades the brief's literal 'at most once per 12 h' for an economic cap: a run that sent amount closes the treasury for 12 h * amount / 0.01 ETH, so a 0.005 ETH run reopens after 6 h and a run under about 2.3e11 wei reopens at once. The ETH per 12 hours is still capped at 0.01 ETH plus one run, and the money only ever goes to the swarm's wallet, so no loss; recorded because it is a literal departure the requester should confirm (reported by write_foundry_tests as info).","line":41,"path":"src/HeartbeatTreasury.sol","reproduction":"test/scratch/Preempt.t.sol::test_treasuryTwoRunsInOneBlock: deal the treasury 1 wei, fundNextRun() sends it and nextRunAt == block.timestamp; deal 1 wei again and fundNextRun() in the same block succeeds. The brief's literal rule would revert TooSoon.","severity":"info","snippet":"        nextRunAt = block.timestamp + INTERVAL * amount / CAP_PER_RUN;","title":"fundNextRun() may now run more than once per 12 hours when the treasury holds less than the cap; the brief says 'at most once per 12 h'"},{"citation":"resolved","description":"Settling my earlier low finding, which the author disputed. The behaviour is unchanged and reproducible, and the dispute is sound: the pot holds ETH, the cost is IMD, no contract here has a rate, the brief names no ETH figure and forbids an owner who could set one, and the manifest rules refuse stand-in values. The README now states that judging costs the caller more than it pays until the pot is above roughly 17 x the IMD price in ETH and that the swarm's heartbeat is expected to judge then. Dropped as a defect; kept here so the requester decides whether to name a fixed ETH reimbursement.","line":265,"path":"src/MeatbagGame.sol","reproduction":"Pot 0.001 ETH (one entry). Next day the keeper calls judge(): the keeper's IMD balance falls by judgePrice() (0.5e18 on the live Intake, read with cast on 2026-10-09) and claimable[keeper] == 3e13 wei; nothing else is credited.","severity":"info","snippet":"        uint256 reward = pot * KEEPER_BPS / 10_000;\n        pot -= reward;\n        _credit(msg.sender, reward);","title":"judge() still repays nothing for the 0.5 IMD, only 3% of the pot; the author's dispute stands and the choice is the requester's"},{"citation":"resolved","description":"Settling my earlier info note and audit_economics' medium: it follows the brief's parameters (one entry per wallet, 40 slots at k x 0.001 ETH) and any per-person limit beyond the wallet needs identity the chain does not have. The author documented it in the README with the break-even (profitable once the pot is above about 0.24 ETH). Not a code defect.","line":205,"path":"src/MeatbagGame.sol","reproduction":"Pot 10 ETH. 40 fresh wallets each enter with nextSlotPrice() (0.82 ETH in total); nextSlotPrice() == 0 and enter() reverts RoundFull for everyone else. Next day judge() and a valid attestation naming any index 0..39 credits a funder wallet about 8.4 ETH.","severity":"info","snippet":"        if (hasEntered[day][msg.sender]) revert AlreadyEntered();","title":"Design property: one funder can take all 40 slots of a round for 0.82 ETH and is certain to win it (and most of a sunset)"},{"citation":"resolved","description":"Settling my earlier info note, which the author disputed with the brief's own requirement that every heartbeat job ends with a Message on what it built: free text from that wallet is required, so 'no caller-supplied text' can only mean no public caller. The README states the assumption. Not a code defect.","line":64,"path":"src/MeatbagHerald.sol","reproduction":"vm.prank(0xd01122bBfFd00fc96252c8b29867a5359a3bca13); herald.post('migrate at 0x1234') emits Message(TO, ...) and count increments; any other sender reverts NotAuthorised.","severity":"info","snippet":"        if (msg.sender != SWARM) revert NotAuthorised();","title":"Trust assumption: the swarm wallet 0xd011...bca13 can post any text on the only official channel and nothing can revoke it"}],"hash":"bef9efea603593ce8f80c786ca7e6ff1c8e5aabf6e3958cb368f4eb2b19a8cab","nodeId":"d324c57d-6451-4349-ad02-f176c0dddef3","outcome":"completed","summary":"The review is complete. The findings file is written at `.imd-findings.json`, all snippets match the tree, and no project file was changed.\n\n**Earlier findings settled**\n\n- **High, stranded pot when the Intake refuses:** fixed. My proof passes: a round nobody can judge becomes hangable at its deadline and counts toward the sunset. The audit_permissions proof still fails, but only because its one-round scenario expects recovery without the brief's seven-round rule. The round is in fact Hung with streak 1. I also confirmed with cast that the live Intake answers 0.5 IMD for the action, so the new price read works on mainnet.\n- **Medium, fee on amountSpecified for partial fills:** fixed. Price-limited swaps with ETH as the specified side now revert with PartialFill, and volume comes from the settled delta. The proof passes and the mainnet fork rehearsal passes on the real PoolManager and Intake.\n- **Low, exact-output buy rate, seventh hung in callback, ETH stuck in hook, heartbeat dust slot:** all fixed and verified by reading and by the project suite, which passes 127 tests plus 6 fork tests.\n- **Low, judge repayment and the two info notes:** the author's disputes are sound. Kept as info records only.\n\n**One real new defect, from the test writer's lead**\n\n- **Medium: the game's first-ever verdict exceeds the 200,000 gas stipend when the keeper claims before delivery.** I measured the callback's minimum gas by binary search. The first round needs 206,156 gas when totalClaimable is zero, 189,056 when it is not, and later rounds fit with room. The first round's winner goes unpaid and the keeper's IMD is lost, after which the game recovers. The specialist's proof fails on this tree for that reason and is attached.\n\n**Advisory**\n\n- **Low:** the new declareHungJury path can hang a round that judge() could still send to the panel, from 01:00 UTC two days after its day. A stranger can front-run the keeper.\n- **Info:** fundNextRun now runs more than once per 12 hours when below the cap, a literal departure from the brief that the dust-slot fix traded for.\n\nCoverage answers all 22 entry points plus two invariant rows. The one thing I could not do is observe the live Intake's exact stipend wrapper, so the gas finding rests on the reference's stated 200,000 figure.","treeHash":null,"usage":{"cachedInputTokens":1864225,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":34933,"runtime":"claude","turns":28,"wallClockMs":834145}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2dc755dfe7bd177c","findings":[{"citation":"resolved","description":"Math Precision / Asymmetry seam. For an exact-input buy the fee is `amountIn * rate / 10_000`, i.e. `rate` of the ETH the buyer spends (src/MeatbagHook.sol:224). For an exact-output buy the fee is computed in afterSwap as `rate` of the ETH the pool took (line 261) and then added on top of what the buyer owes, so the buyer spends poolEth + fee and the effective rate on their spend is rate/(10_000+rate): 2000 bps instead of 2500 at launch, 1200 instead of 1350 at minute 15, 196 instead of 200 after the decay. The sell side does the gross-up correctly (line 229: exact-output sells pay 2% of the gross), so only the exact-output buy branch is on a different basis. The brief says buys pay 25% decaying to 2% with the surplus to the pot; a launch sniper who submits exact-output buys pays 20% of their ETH and the pot receives 18% of their spend instead of 23%. The README documents the 1.96% at 2% but not the 20%/25% gap in the decay window the brief defines. Fix: gross the fee up in the exact-output buy branch exactly as the exact-output sell branch does: `fee = poolEth * rate / (10_000 - rate)`, so paid = poolEth + fee and fee/paid = rate/10_000.","line":261,"path":"src/MeatbagHook.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} from \"v4-core/src/types/Currency.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {MeatbagHook} from \"src/MeatbagHook.sol\";\nimport {MeatbagToken} from \"src/MeatbagToken.sol\";\nimport {MeatbagGame} from \"src/MeatbagGame.sol\";\nimport {HeartbeatTreasury} from \"src/HeartbeatTreasury.sol\";\n\n/// @notice Finding: an exact-output buy pays the buy fee on the ETH the pool took, not on the ETH the\n/// buyer spends, so at launch (25% rate) it pays 20% of its spend while an exact-input buy pays 25%.\n/// Fails on the current code; passes once the exact-output buy fee is grossed up\n/// (fee = poolEth * rate / (10_000 - rate)) so both buy paths charge `rate` of the buyer's ETH.\ncontract ProofExactOutBuyBasis is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    address constant SWARM = 0xd01122bBfFd00fc96252c8b29867a5359a3bca13;\n\n    IPoolManager manager;\n    MeatbagToken token;\n    MeatbagHook hook;\n    MeatbagGame game;\n    HeartbeatTreasury treasury;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        token = new MeatbagToken();\n        bytes memory creationCode =\n            abi.encodePacked(type(MeatbagHook).creationCode, abi.encode(address(manager), address(this)));\n        bytes32 initCodeHash = keccak256(creationCode);\n        address at;\n        for (uint256 i = 0; i < 500_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            require(at == predicted, \"hook deployment reverted\");\n            break;\n        }\n        hook = MeatbagHook(payable(at));\n        game = hook.game();\n        treasury = hook.treasury();\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        key = PoolKey({\n            currency0: Currency.wrap(address(0)),\n            currency1: Currency.wrap(address(token)),\n            fee: 12500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.deal(address(this), 1_000_000 ether);\n        token.approve(address(lpRouter), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n        lpRouter.modifyLiquidity{value: 2_000 ether}(\n            key, ModifyLiquidityParams(-887220, 887220, 1_000 ether, bytes32(0)), \"\"\n        );\n    }\n\n    function feeTotal() internal view returns (uint256) {\n        return game.pot() + SWARM.balance + address(treasury).balance + address(hook).balance;\n    }\n\n    function test_exactOutputBuyPaysTheBuyRateOnTheEthSpent() public {\n        uint256 rate = hook.buyFeeBps();\n        assertEq(rate, 2500, \"launch rate\");\n        uint256 ethBefore = address(this).balance;\n        swapRouter.swap{value: 10 ether}(\n            key, SwapParams(true, int256(1 ether), TickMath.MIN_SQRT_PRICE + 1), PoolSwapTest.TestSettings(false, false), \"\"\n        );\n        uint256 paid = ethBefore - address(this).balance;\n        uint256 fee = feeTotal();\n        // The brief: 25% at launch on buys. An exact-input buy of `paid` ETH would pay paid * 2500 / 10_000.\n        assertApproxEqAbs(fee, paid * rate / 10_000, 2, \"exact-output buy must pay the same rate of its ETH as exact-input\");\n    }\n}","reproduction":"Pool seeded 1000e18 liquidity at 1:1 (test/HookTestBase.setUpPool(true)), block.timestamp == launchedAt (buyFeeBps() == 2500). Call swapRouter.swap{value: 10 ether}(key, SwapParams(true, +1e18, MIN_SQRT_PRICE+1)) (exact-output buy of 1 MEAT). Observed: buyer spends 1.267089874684811395 ETH, fee taken 0.253417974936962279 ETH = 2000 bps of the spend (2500 bps of the 1.013671899747849116 ETH the pool took). Expected per brief: 25% of the buyer's ETH = 0.316772468671202848 ETH; the pot is short 0.063354493734240569 ETH on this one trade. Control: an exact-input buy spending the same 1.267089874684811395 ETH pays 0.321840828169942093 ETH (2500 bps) and receives only 0.9376 MEAT for it.","severity":"medium","snippet":"            fee = poolEth * rate / 10_000;","title":"Exact-output buys pay the buy fee on the pool's ETH, not the buyer's: 20% instead of 25% at launch, 1.96% instead of 2% after"},{"citation":"resolved","description":"Boundary x precision seam at the PoolManager boundary. On the two paths where ETH is the specified currency (exact-input buy, line 224; exact-output sell, line 229) the fee is fixed in beforeSwap from `params.amountSpecified` and credited to the hook as a BeforeSwapDelta before the pool runs. v4 does not revert a swap that reaches `sqrtPriceLimitX96`: it stops early and the settled delta is smaller than the specified amount. The hook never compares the settled `delta.amount0()` with the amount it priced, and afterSwap (line 256) records `ethMoved` from amountSpecified as well. The brief requires the fee to be taken 'from the settled ETH delta'. Consequences: (a) an exact-input buyer whose limit is hit pays rate x amountSpecified for ETH the pool never consumed; (b) an exact-output seller whose limit is hit receives less than asked but still owes the full grossed-up fee, and when the pool pays nothing the seller ends with a negative ETH delta, i.e. pays the hook to sell; (c) `volume` and the herald's 1/10/100 ETH milestone letters are advanced by the specified amount, so for a 2 ETH fee anyone can post all three volume milestones with a trade that moved 0 ETH. Only direct PoolManager/PoolSwapTest-style callers and integrators that set a real price limit are exposed; routers that pass MIN/MAX limits are not. Fix: in afterSwap, for the ETH-specified paths, derive the fee from the settled ETH (`|delta.amount0()|`) and either refund the difference or, simpler, revert with a PartialFill error when `|delta.amount0()|` is less than the amount the fee was computed on; record volume from the settled amount.","line":224,"path":"src/MeatbagHook.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 {PoolId} from \"v4-core/src/types/PoolId.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {MeatbagHook} from \"src/MeatbagHook.sol\";\nimport {MeatbagToken} from \"src/MeatbagToken.sol\";\nimport {MeatbagGame} from \"src/MeatbagGame.sol\";\nimport {HeartbeatTreasury} from \"src/HeartbeatTreasury.sol\";\n\n/// @notice Finding: on the two paths where ETH is the specified currency (exact-input buy, exact-output\n/// sell) the fee is fixed in beforeSwap from `amountSpecified`, not from the ETH that actually settles.\n/// A swap stopped by its price limit moves ~0 ETH yet pays the full fee and is counted as full volume.\n/// Fails on the current code; passes once the hook either charges on the settled ETH delta or refuses\n/// a swap whose settled ETH is short of the amount the fee was computed on.\ncontract ProofFeeOnUnfilledAmount is Test {\n    using StateLibrary for IPoolManager;\n\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    address constant SWARM = 0xd01122bBfFd00fc96252c8b29867a5359a3bca13;\n\n    IPoolManager manager;\n    MeatbagToken token;\n    MeatbagHook hook;\n    MeatbagGame game;\n    HeartbeatTreasury treasury;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        manager = new PoolManager(address(this));\n        token = new MeatbagToken();\n        bytes memory creationCode =\n            abi.encodePacked(type(MeatbagHook).creationCode, abi.encode(address(manager), address(this)));\n        bytes32 initCodeHash = keccak256(creationCode);\n        address at;\n        for (uint256 i = 0; i < 500_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            require(at == predicted, \"hook deployment reverted\");\n            break;\n        }\n        hook = MeatbagHook(payable(at));\n        game = hook.game();\n        treasury = hook.treasury();\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        key = PoolKey({\n            currency0: Currency.wrap(address(0)),\n            currency1: Currency.wrap(address(token)),\n            fee: 12500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        manager.initialize(key, SQRT_PRICE_1_1);\n        vm.deal(address(this), 1_000_000 ether);\n        token.approve(address(lpRouter), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n        lpRouter.modifyLiquidity{value: 2_000 ether}(\n            key, ModifyLiquidityParams(-887220, 887220, 1_000 ether, bytes32(0)), \"\"\n        );\n        vm.warp(hook.launchedAt() + 30 minutes); // 2% flat\n    }\n\n    function feeTotal() internal view returns (uint256) {\n        return game.pot() + SWARM.balance + address(treasury).balance + address(hook).balance;\n    }\n\n    /// An exact-input buy of 100 ETH with the price limit one unit below the current price: the pool\n    /// consumes no ETH. The fee must then be nothing (or the swap refused), not 2 ETH.\n    function test_unfilledExactInputBuyIsNotChargedTwoEth() public {\n        (uint160 price,,,) = manager.getSlot0(key.toId());\n        uint256 ethBefore = address(this).balance;\n        uint256 tokensBefore = token.balanceOf(address(this));\n        try swapRouter.swap{value: 100 ether}(\n            key, SwapParams(true, -int256(100 ether), price - 1), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            uint256 tokensOut = token.balanceOf(address(this)) - tokensBefore;\n            uint256 paid = ethBefore - address(this).balance;\n            uint256 fee = feeTotal();\n            // 2% of the ETH that actually bought tokens, with 1 wei of rounding room.\n            assertLe(fee, (paid - fee) * 200 / 10_000 + 1, \"fee charged on 100 ETH that never moved\");\n            assertLe(hook.volume(), paid, \"volume counted ETH that never moved\");\n            assertTrue(tokensOut > 0 || fee == 0, \"paid a fee and received nothing\");\n        } catch {\n            // Refusing the partially-filled swap is also an acceptable fix.\n        }\n    }\n\n    /// An exact-output sell of 1 ETH with the price limit one unit above the current price: the pool\n    /// pays out nothing, yet the seller ends up paying the grossed-up fee of 0.0204 ETH to the hook.\n    function test_unfilledExactOutputSellDoesNotMakeTheSellerPayEth() public {\n        (uint160 price,,,) = manager.getSlot0(key.toId());\n        uint256 ethBefore = address(this).balance;\n        try swapRouter.swap{value: 1 ether}(\n            key, SwapParams(false, int256(1 ether), price + 1), PoolSwapTest.TestSettings(false, false), \"\"\n        ) {\n            assertGe(address(this).balance, ethBefore, \"a seller must never pay ETH\");\n        } catch {\n            // Refusing the partially-filled swap is also an acceptable fix.\n        }\n    }\n}","reproduction":"Pool seeded as in test/HookTestBase.setUpPool(true), warp to launchedAt + 30 minutes (2% flat). Read current sqrtPrice P via getSlot0. (a) swapRouter.swap{value: 100 ether}(key, SwapParams(true, -100e18, P - 1)): observed BalanceDelta amount0 = -2000000000000000002 wei, amount1 = 0 (zero MEAT received); pot +1.1 ETH, swarm +0.5 ETH, treasury +0.4 ETH (fee = 2 ETH on 100 ETH specified, 2 wei consumed); hook.volume() == 100e18 and the herald emits the 1, 10 and 100 ETH letters. Expected: fee on the ~0 ETH that settled (0) or a revert. (b) swapRouter.swap{value: 1 ether}(key, SwapParams(false, +1e18, P + 1)): observed amount0 = -20408163265306122 (the seller PAYS 0.0204 ETH and receives 0 ETH), fee 20408163265306122, volume +1020408163265306122. Expected: a seller never pays ETH; fee 2% of the 0 ETH that left the pool.","severity":"medium","snippet":"            fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;","title":"Fee is charged on the specified ETH amount, not the settled ETH delta: a price-limited swap pays the full fee on ETH that never moved, and a partially filled exact-output sell makes the seller pay ETH"},{"citation":"resolved","description":"Boundary (external call that reverts). `judge()` is the only path that moves a round from Open to Pending, and `declareHungJury()` (line 255) and the callback both require Pending. The intake call is not wrapped: if `request` reverts (the action `oracle.request@oracle-1` retired or renamed, the price of 0.5 IMD raised, the asset no longer accepted, the IMD token transfer failing), `judge()` reverts every time, the round at `cursor` stays Open forever, `unsettledStreak` never advances, `_sunset` is unreachable and `claimSunset` has nothing to pay. All ETH in `pot` (entry fees plus 55% of every hook fee ever taken, which keeps growing) is then permanently locked: there is no owner, no refund and no other exit. The README's assumption section states the opposite ('if IMD rotates its signer, retires `oracle-1` or moves the price, rounds stop settling and the sunset rule returns the pot to entrants'): that holds only for the signer rotation (requests still go out and time out); a refused request never reaches the timeout path. The game pins INTAKE, ACTION and PRICE as immutables, so this is a lifetime dependency on one external price and action id. Fix: let a round that stayed Open for longer than VERDICT_TIMEOUT after it closed be declared hung by anyone (extend `declareHungJury` to `status == Open && today() > day + 1 + timeout`), or wrap `request` in try/catch and on failure mark the round Hung without pulling the keeper's IMD (move the transferFrom after the request, or refund).","line":239,"path":"src/MeatbagGame.sol","reproduction":"Deploy MeatbagGame with an intake whose `request` reverts (e.g. a contract that reverts 'ActionNotSold', as the real Intake does for a retired action). Fund the pot with 10 ETH via receive(); alice calls enter{value: 0.001 ether}('a'); warp to the next day. keeper (holding and having approved 0.5 IMD) calls judge(): reverts with the intake's error. Warp 30 days: judge() still reverts; declareHungJury() reverts NotPending(day); claimSunset(day) reverts NothingToClaim; pot == 10.001 ETH, unsettledStreak == 0, cursor == 0. Expected: after a bounded time the entrants (or the sunset rule) can recover the pot; actual: no public call can ever move the state. Scratch reproduction: test/scratch/GameMath.t.sol::test_C_refusingIntakeLocksThePot.","severity":"medium","snippet":"        intakeRequestId = IIntake(INTAKE)\n            .request(ACTION, judgeBody(day), IIntake.Callback(address(this), this.onOracleResult.selector), IMD, PRICE);","title":"If the Intake refuses the request, no round can ever become Pending or Hung, so the sunset rule never fires and the pot is locked for good"},{"citation":"resolved","description":"Boundary x invariant. `receive()` must exist so `poolManager.take` can deliver fee ETH, but it also accepts ETH from anyone. `_distribute` (lines 343-349) pays min(owed, balance) per recipient and leaves the rest; every wei the hook ever owes is matched one-for-one by a `take`, so a balance that exceeds the owed total can never be assigned to anyone and never leaves the contract. There is no sweep. Fix: in `_distribute`, send any balance left after the three owed amounts to the game (the pot) so a stray transfer or donation feeds the pot like a donation to the game would.","line":112,"path":"src/MeatbagHook.sol","reproduction":"After setUpPool(true) and the decay window: `(bool ok,) = address(hook).call{value: 1 ether}('')` succeeds. hook.distribute(): hook balance still 1 ether, owedPot == owedSwarm == owedTreasury == 0. buyExactIn(1 ether); sellExactIn(1 ether); hook.distribute(): hook balance still exactly 1 ether, all owed 0. Expected: a donation to the project reaches the pot; actual: it is unreachable by any call. Scratch: test/scratch/HookMath.t.sol::test_D_strayEthStuckInHook.","severity":"low","snippet":"    receive() external payable {}","title":"ETH sent to the hook's receive() is stuck forever: _distribute only pays what is owed, never the surplus"},{"citation":"resolved","description":"Boundary (gas at the intake callback). The oracle-consumer reference gives the callback 200,000 gas and states a callback that runs out of gas 'is recorded as not delivered and is not retried'. When `onOracleResult` records a weak panel, out-of-range index or wrong answer type as hung and `unsettledStreak` reaches 7, `_hung` runs `_sunset` inline: 7 cold roundDays/count reads plus 7 `sunsetShare` SSTOREs (20k each) on top of the verdict bookkeeping. Measured minimum gas to deliver: 291,874 (first hung in callback: 150,540; first verdict with a full round: 189,044; later verdict: 174,427). So the seventh-in-a-row 'no' from the panel always fails delivery; the request stays Pending, the keeper's 0.5 IMD is gone, and only `declareHungJury()` after VERDICT_TIMEOUT (25 h) performs the sunset. The README lists this as a known item; it is reported here because it is deterministic and the brief's 'no verdict = hung jury' settlement is delayed a day for the entrants of all seven rounds. Also note the ordinary first-verdict path uses 189k of the 200k budget (5.5% headroom). Fix: do not run `_sunset` from the callback: have `_hung` only bump the streak and let anyone call a public `sunset()` (or `declareHungJury`) that performs the loop with full gas; or write the per-round share lazily in `claimSunset` from a single stored (firstIndex, share) pair.","line":399,"path":"src/MeatbagGame.sol","reproduction":"Fund the pot with 7 ETH. For i in 0..5: alice enters, next day, judge(), warp VERDICT_TIMEOUT, declareHungJury() (streak == 6). alice enters, next day, judge() -> id. From the intake address call onOracleResult(id, attestation with agreed = 3 < quorum 4, valid signature) with 200,000 gas (MockIntake.deliver): returns ok == false; round status remains Pending (2), unsettledStreak remains 6, no sunsetShare set. Binary search of the gas limit shows the call first succeeds at 291,874 gas. Expected: the hung jury and sunset are recorded on delivery. Scratch: test/scratch/GameMath.t.sol::test_gas_hungInCallbackWithStreakSixSunsets.","severity":"low","snippet":"        if (unsettledStreak >= SUNSET_AFTER) _sunset();","title":"The seventh consecutive hung verdict delivered through the oracle callback needs ~292k gas, above the 200k stipend: it is never delivered, the 0.5 IMD is spent and settlement waits 25 hours"},{"citation":"resolved","description":"Economics note outside the math area, recorded for the judge. The brief says the judge caller 'is repaid from the pot plus 3% of it'. The code credits exactly pot * 300 / 10_000 in ETH and nothing for the 0.5 IMD it pulls; the README documents this as a deliberate reading (no price feed to value IMD). Consequence: while 3% of the pot is worth less than 0.5 IMD plus gas, no rational keeper calls judge(), closed rounds queue up unjudged and the pot carries over until it is large enough; this is self-correcting, not a loss. No change is required if the requester accepts the README's reading; otherwise the repayment needs a fixed ETH-denominated reimbursement constant.","line":233,"path":"src/MeatbagGame.sol","reproduction":"test/MeatbagGame.t.sol::test_judgePaysTheIntakeAndRewardsTheKeeper: with pot P before judge(), keeper's claimable == P * 3 / 100 and the keeper's IMD balance is 0.5 IMD lower; no other credit exists.","severity":"info","snippet":"        uint256 reward = pot * KEEPER_BPS / 10_000;","title":"judge() pays the keeper only 3% of the pot; the brief's 'repaid from the pot plus 3% of it' repayment of the 0.5 IMD is not made"}],"hash":"cfa80ca31d519bf647324a31974626538ec41b868d33563e8154bc4cc9ec0d56","nodeId":"5b984ddb-5b55-4523-a989-aeb774b518fb","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` with six findings and a 24-row coverage record. All snippets match the tree, and the project's own 62 tests pass on the reviewed code.\n\n**What I found** in the Math Precision, Boundary and Numerical Gap area:\n\n1. **Medium. Exact-output buys use a different fee basis** than exact-input buys. The fee is 25% of the pool's ETH, so the buyer pays 20% of their spend at launch, not 25%. The pot received 0.0634 ETH less on one 1.27 ETH trade in the reproduction. A self-contained proof test is attached.\n2. **Medium. The fee on ETH-specified swaps is fixed from the specified amount, not the settled delta.** A 100 ETH exact-input buy stopped at its price limit received zero tokens, still paid a 2 ETH fee, and advanced volume by 100 ETH, which fires all three herald volume letters. A partially filled exact-output sell made the seller pay 0.0204 ETH. Proof test attached.\n3. **Medium. If the Intake ever refuses the request**, no round can become Pending, so neither the hung-jury nor the sunset path can run and the whole pot is locked forever. The README claims the sunset covers this case. It does not.\n4. **Low. ETH sent directly to the hook is stuck** because distribution only pays amounts owed.\n5. **Low. The seventh consecutive hung verdict delivered via the callback needs 292k gas**, above the 200k stipend, so it is never delivered. The author documents this. The first verdict path uses 189k of the 200k budget.\n6. **Info.** The keeper receives only 3% of the pot and no repayment for the 0.5 IMD, a documented reading of the brief.\n\n**What held:** the 55/25/20 split and the decay formula are exact on all four swap paths, the sell gross-up is correct, claim and sunset accounting keep balance equal to pot plus claimable, the attestation checks (signer, domain, expiry, issuedAt, replay, index range) behave as intended, and the heartbeat cap and interval are correct.\n\n**Not reached:** the mainnet fork rehearsal under `test/fork` was not run, since it needs network access.","treeHash":null,"usage":{"cachedInputTokens":2845229,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":65284,"runtime":"claude","turns":41,"wallClockMs":942138}},{"artifacts":[],"attempt":1,"bundleHash":"a4ac06d88c94e021bc55c39aba4f28a5483f3a855e2bdb1d097fa23ca5929923","device":"4ca9ed4f0937da89","findings":[],"hash":"f1efefa9ff9a58222ac1b08266e48e7b599ef8933fb429d9349dd73a202d6222","nodeId":"410fe7f3-5cae-4cfd-b1bd-fb69ed44ee5b","outcome":"completed","summary":"I tightened the tests around the implementation's latest change, and I found no new defects. `forge test` passes: 128 tests in 13 suites, none failing or skipped. The six mainnet fork tests (`FOUNDRY_PROFILE=fork`) also pass against the real PoolManager.\n\n**What changed in the implementation:** the earlier finding was that the first winning verdict needed about 211,000 gas, more than the oracle writer's 200,000. The other contributor fixed it by moving the herald's first-verdict announcement out of the oracle callback. The letter is now posted once, by the next `judge()`, `declareHungJury()`, `sunset()`, `claim()` or the new public `announceFirstVerdict()`.\n\n**What I changed (only `test/`):**\n- **`test/invariant/GameInvariant.t.sol`**\n  - The handler used to count valid oracle answers that ran out of the 200,000 gas limit and carry on. A new invariant, `invariant_validAnswersFitTheStipend`, now fails if that happens even once, so the fix can't quietly regress.\n  - A new invariant, `invariant_firstVerdictLetterFollowsASettledRound`, checks that the herald posts the first-verdict letter only after a round has actually settled, and never twice.\n  - The handler now checks that the oracle callback itself never posts the letter.\n  - It also checks that once a verdict has landed, every public call that completes leaves the letter posted. A new `announce()` action exercises `announceFirstVerdict()`.\n- **`test/MeatbagGameEdge.t.sol`**: four new failure-path tests:\n  - A hung jury from the oracle never posts the first-verdict letter.\n  - `declareHungJury()` posts a waiting first-verdict letter before hanging the next round, with each letter posted exactly once.\n  - A second verdict doesn't post the letter again.\n  - A claim that reverts doesn't post the letter; the next successful claim does.\n\nI didn't write a `.imd-findings.json` because there are no new defects. One thing I checked by reading the code rather than by a test: every path that can advance past a settled round runs the announcement first, so the first-verdict letter can't be skipped. The new handler checks cover this over random call sequences, but I didn't confirm that those runs actually reached a winning verdict.\n\nI couldn't run the protected launch tests locally: their setup needs an `IMD_HOOK_CREATION_CODE` environment variable that only the verifier's toolchain provides.","treeHash":"b8d786ca729cd31b79f80d53c84b65b91a8d26e6","usage":{"cachedInputTokens":1176954,"inputTokens":36,"model":"claude-opus-5-5","outputTokens":8712,"runtime":"claude","turns":18,"wallClockMs":234467}}],"verification":[{"checks":[{"durationMs":26802,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 26.66s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:420:5:\n    |\n420 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\n   │                 ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:321:9\n    │\n321 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:45:38\n   │\n45 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\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/MeatbagGame.sol:149:57\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:149:40\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:189:39\n    │\n189 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:204:30\n    │\n204 │         _rounds[day].count = uint8(slot + 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[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/MeatbagHook.sol:98:44\n   │\n98 │     constructor(IPoolManager poolManager_, address factory_) {\n   │                                            ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:398:9\n    │\n398 │         herald.announce(_H_FIRST_HUNG_JURY);\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/MeatbagHook.sol:142:13\n    │\n142 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:387:13\n    │\n387 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:224:17\n    │\n224 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:247:9\n    │\n247 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:243:25\n    │\n243 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:256:13\n    │\n256 │         if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:224:19\n    │\n224 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MeatbagGame.sol:397:9\n    │\n397 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:417:9\n    │\n417 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:228:30\n    │\n228 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:234:63\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:234:70\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagGame.sol:300:9\n    │\n300 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:292:20\n    │\n292 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:336:13\n    │\n336 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:330:13\n    │\n330 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:331:13\n    │\n331 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:334:13\n    │\n334 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:353:26\n    │\n353 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:358:26\n    │\n358 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:363:26\n    │\n363 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:268:44\n    │\n268 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:324:9\n    │\n324 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:332:13\n    │\n332 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:366:18\n    │\n366 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:369:9\n    │\n369 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:256:34\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:256:69\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:413:31\n    │\n413 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:45\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:53\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:74\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:82\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:407:13\n    │\n407 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:263:32\n    │\n263 │             unspecifiedDelta = 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/MeatbagHook.sol:263:39\n    │\n263 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:295:9\n    │\n295 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:304:9\n    │\n304 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:318:27\n    │\n318 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:319:30\n    │\n319 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/MeatbagGame.t.sol:92:30\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n92 │             issuedAt: uint64(block.timestamp),\n   │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:93:31\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n93 │             expiresAt: uint64(block.timestamp + 1 days)\n   │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:387:17\n    │\n387 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:426:21\n    │\n426 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:466:21\n    │\n466 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:482:17\n    │\n482 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":192,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_anyoneMayCallIt() (gas: 86427)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 91310)\n[PASS] test_hasNoAdminSurface() (gas: 49068)\n[PASS] test_onlyOncePerTwelveHours() (gas: 174324)\n[PASS] test_revertsWhenEmpty() (gas: 31992)\n[PASS] test_sendsWhatItHasWhenBelowTheCap() (gas: 85978)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 533.79µs (747.59µs CPU time)\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 684.77µs (262.76µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 723.27µs (836.69µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2006995)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2017446)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10282)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.39ms (1.26ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 17593141)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.76ms (4.32ms CPU time)\n\nRan 22 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aVerdictResetsTheStreak() (gas: 5419517)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 1789793)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1888848)\n[PASS] test_callbackRefusesReplay() (gas: 3168519)\n[PASS] test_callbackRefusesStrangers() (gas: 1755136)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1933714)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1809135)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1765730)\n[PASS] test_donationsFeedThePot() (gas: 60094)\n[PASS] test_fortyEntriesThenFull() (gas: 16404912)\n[PASS] test_hasNoAdminSurface() (gas: 146994)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329381)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2419150)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 317043)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2826729)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3966862)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687496)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10195010)\n[PASS] test_slotPricingAndLimits() (gas: 527147)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 1991416)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2350206)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3338334)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 23.98ms (22.57ms CPU time)\n\nRan 18 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 393510, ~: 400703)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 42157387)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397192)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthThePoolTook() (gas: 383088)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77882)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 42262075)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 141981)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49858)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 372860)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541506)\n[PASS] test_hasNoOwnerOrSetters() (gas: 119991)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349115)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 22337363)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8929)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 33272566)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344198)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349481)\n[PASS] test_sellsNeverDecay() (gas: 321879)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 43.28ms (124.46ms CPU time)\n\nRan 7 test suites in 43.83ms (75.36ms CPU time): 60 tests passed, 0 failed, 0 skipped (60 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":232,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":188,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":427,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":381,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":62,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":512,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":291,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":62},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":5909,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/MeatbagHook.sol:342: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#342-370):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:342: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#342-370):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:342: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#342-370):\n[high/high] uninitialized-state at src/MeatbagGame.sol:174: MeatbagGame._entries (src/MeatbagGame.sol#144) is never initialized. It is used in:\n[medium/medium] divide-before-multiply at src/MeatbagGame.sol:403: MeatbagGame._sunset() (src/MeatbagGame.sol#403-418) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:314: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:314: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/MeatbagGame.sol:318: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#318-326) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:342: MeatbagHook._distribute() (src/MeatbagHook.sol#342-370) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/HeartbeatTreasury.sol:30: HeartbeatTreasury.nextRunAt() (src/HeartbeatTreasury.sol#30-32) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagGame.sol:263: MeatbagGame.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/MeatbagGame.sol#263-302) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:138: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#138-144) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:314: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:293: MeatbagHook.redeemClaims() (src/MeatbagHook.sol#293-297) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/HeartbeatTreasury.sol:35: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#35-45) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:219: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#219-248):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:391: Reentrancy in MeatbagGame._hung(uint256) (src/MeatbagGame.sol#391-400):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:219: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#219-248):\n[medium/medium] reentrancy-no-eth at src/MeatbagHook.sol:314: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:219: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#219-248):\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:351: MeatbagGame.judgeBody(uint256).defs (src/MeatbagGame.sol#351) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:405: MeatbagGame._sunset().entrants (src/MeatbagGame.sol#405) is a local variable never initialized\n[medium/medium] unused-return at src/MeatbagHook.sol:293: MeatbagHook.redeemClaims() (src/MeatbagHook.sol#293-297) ignores return value by poolManager.unlock() (src/MeatbagHook.sol#295)\n[low/medium] missing-zero-check at src/MeatbagHerald.sol:43: MeatbagHerald.constructor(address).game_ (src/MeatbagHerald.sol#43) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:149: MeatbagGame.constructor(MeatbagHerald,address,address,address).imd_ (src/MeatbagGame.sol#149) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagHook.sol:98: MeatbagHook.constructor(IPoolManager,address).factory_ (src/MeatbagHook.sol#98) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:149: MeatbagGame.constructor(MeatbagHerald,address,address,address).intake_ (src/MeatbagGame.sol#149) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:391: Reentrancy in MeatbagGame._hung(uint256) (src/MeatbagGame.sol#391-400):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:219: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#219-248):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:391: Reentrancy in MeatbagGame._hung(uint256) (src/MeatbagGame.sol#391-400):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:219: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#219-248):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:342: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#342-370):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:342: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#342-370):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:293: Reentrancy in MeatbagHook.redeemClaims() (src/MeatbagHook.sol#293-297):\n[low/medium] reentrancy-events at src/HeartbeatTreasury.sol:35: Reentrancy in HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#35-45):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:299: Reentrancy in MeatbagHook.unlockCallback(bytes) (src/MeatbagHook.sol#299-306):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:314: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:314: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340):\n[low/medium] timestamp at src/MeatbagGame.sol:212: MeatbagGame.nextRoundToJudge() (src/MeatbagGame.sol#212-214) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:293: MeatbagHook.redeemClaims() (src/MeatbagHook.sol#293-297) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:251: MeatbagGame.declareHungJury() (src/MeatbagGame.sol#251-258) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:154: MeatbagHook.beforeInitialize(address,PoolKey,uint160) (src/MeatbagHook.sol#154-163) uses timestamp for comparisons\n[low/medium] timestamp at src/HeartbeatTreasury.sol:35: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#35-45) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:384: MeatbagGame._recordPot() (src/MeatbagGame.sol#384-389) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:314: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#314-340) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:138: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#138-144) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:318: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#318-326) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:342: MeatbagHook._distribute() (src/MeatbagHook.sol#342-370) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:263: MeatbagGame.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/MeatbagGame.sol#263-302) uses timestamp for comparisons\n[low/medium] timestamp at src/HeartbeatTreasury.sol:30: HeartbeatTreasury.nextRunAt() (src/HeartbeatTreasury.sol#30-32) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:219: MeatbagGame.judge() (src/MeatbagGame.sol#219-248) uses timestamp for comparisons","passed":true},{"durationMs":656,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/HeartbeatTreasury.sol:35: ETH transferred without address checks (5 places)\n[high] reentrancy-state-change at src/MeatbagGame.sol:239: Reentrancy: State change after external call\n[high] unsafe-casting at src/MeatbagHook.sol:234: Unsafe Casting of integers\n[low] costly-loop at src/MeatbagGame.sol:410: Costly operations inside loop\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once\n[low] large-numeric-literal at src/MeatbagGame.sol:233: Large Numeric Literal (10 places)\n[low] literal-instead-of-constant at src/MeatbagGame.sol:233: Literal Instead of Constant (13 places)\n[low] require-revert-in-loop at src/MeatbagGame.sol:187: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/MeatbagHook.sol:158: Address State Variable Set Without Checks\n[low] unchecked-return at src/MeatbagHook.sol:295: Unchecked Return\n[low] unused-error at src/MeatbagHook.sol:44: Unused Error\n[low] unused-public-function at src/MeatbagGame.sol:174: Public Function Not Used Internally (4 places)\n[low] unused-state-variable at src/MeatbagHook.sol:91: Unused State Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"256b3c4779f6874676ecf58046169056e34a19ff06c771739c663c651d99874a","verifiedTreeHash":"4cf5e4489108b4bf3c62f3b75432c7b6199e68f4","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":28246,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 28.12s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:420:5:\n    |\n420 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:321:9\n    │\n321 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:45:38\n   │\n45 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:149:57\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:149:40\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\n    │                                        ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:189:39\n    │\n189 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:204:30\n    │\n204 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:398:9\n    │\n398 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:387:13\n    │\n387 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:224:17\n    │\n224 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:247:9\n    │\n247 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:243:25\n    │\n243 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagHook.sol:98:44\n   │\n98 │     constructor(IPoolManager poolManager_, address factory_) {\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/MeatbagGame.sol:256:13\n    │\n256 │         if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert NotTimedOut(day);\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/MeatbagHook.sol:142:13\n    │\n142 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\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/MeatbagGame.sol:397:9\n    │\n397 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:417:9\n    │\n417 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:300:9\n    │\n300 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:292:20\n    │\n292 │         r.winner = uint8(index);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:224:19\n    │\n224 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:228:30\n    │\n228 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:234:63\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:234:70\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:413:31\n    │\n413 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:407:13\n    │\n407 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:336:13\n    │\n336 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:330:13\n    │\n330 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:331:13\n    │\n331 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:334:13\n    │\n334 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:353:26\n    │\n353 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:358:26\n    │\n358 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:363:26\n    │\n363 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:268:44\n    │\n268 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:324:9\n    │\n324 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:332:13\n    │\n332 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:366:18\n    │\n366 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:369:9\n    │\n369 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:256:34\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:256:69\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:260:45\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:53\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:74\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:82\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:263:32\n    │\n263 │             unspecifiedDelta = 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/MeatbagHook.sol:263:39\n    │\n263 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:295:9\n    │\n295 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:304:9\n    │\n304 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:318:27\n    │\n318 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:319:30\n    │\n319 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/MeatbagGame.t.sol:92:30\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n92 │             issuedAt: uint64(block.timestamp),\n   │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:93:31\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n93 │             expiresAt: uint64(block.timestamp + 1 days)\n   │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:387:17\n    │\n387 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:426:21\n    │\n426 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:466:21\n    │\n466 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:482:17\n    │\n482 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":156,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 518.06µs (244.32µs CPU time)\n\nRan 6 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_anyoneMayCallIt() (gas: 86427)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 91310)\n[PASS] test_hasNoAdminSurface() (gas: 49068)\n[PASS] test_onlyOncePerTwelveHours() (gas: 174324)\n[PASS] test_revertsWhenEmpty() (gas: 31992)\n[PASS] test_sendsWhatItHasWhenBelowTheCap() (gas: 85978)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 557.72µs (738.58µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 557.94µs (655.01µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2006995)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2017446)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10282)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.41ms (1.29ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 17593141)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.35ms (4.00ms CPU time)\n\nRan 18 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 395147, ~: 400703)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 42157387)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397192)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthThePoolTook() (gas: 383088)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77882)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 42262075)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 141981)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49858)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 372860)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541506)\n[PASS] test_hasNoOwnerOrSetters() (gas: 119991)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349115)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 22337363)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8929)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 33272566)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344198)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349481)\n[PASS] test_sellsNeverDecay() (gas: 321879)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 27.77ms (93.06ms CPU time)\n\nRan 22 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aVerdictResetsTheStreak() (gas: 5419517)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 1789793)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1888848)\n[PASS] test_callbackRefusesReplay() (gas: 3168519)\n[PASS] test_callbackRefusesStrangers() (gas: 1755136)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1933714)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1809135)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1765730)\n[PASS] test_donationsFeedThePot() (gas: 60094)\n[PASS] test_fortyEntriesThenFull() (gas: 16404912)\n[PASS] test_hasNoAdminSurface() (gas: 146994)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329381)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2419150)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 317043)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2826729)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3966862)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687496)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10195010)\n[PASS] test_slotPricingAndLimits() (gas: 527147)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 1991416)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2350206)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3338334)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 27.76ms (20.40ms CPU time)\n\nRan 7 test suites in 28.89ms (62.93ms CPU time): 60 tests passed, 0 failed, 0 skipped (60 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":232,\"foundry.toml\":31,\"launch.json\":30,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":188,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":427,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":381,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":62,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":512,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":291,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":62},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2a6be31d8279e8d352698a642af9229c8031d5a8c0370668cb94f56a2c905e2b","verifiedTreeHash":"b66b1c145b21cff4d92ebe1886c373c97958c194","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":34999,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.84s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:497:5:\n    |\n497 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:381:9\n    │\n381 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:164:40\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:164:57\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\n    │                                                         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:204:39\n    │\n204 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:219:30\n    │\n219 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:461:9\n    │\n461 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:447:13\n    │\n447 │             herald.announce(_H_POT_RECORD);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\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/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagGame.sol:261:17\n    │\n261 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:494:9\n    │\n494 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:460:9\n    │\n460 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:285:9\n    │\n285 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:281:25\n    │\n281 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:296:13\n    │\n296 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\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/MeatbagGame.sol:360:9\n    │\n360 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:352:20\n    │\n352 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `claimable` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\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/MeatbagGame.sol:372:9\n    │\n372 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:490:31\n    │\n490 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:484:13\n    │\n484 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:480:17\n    │\n480 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:519:21\n    │\n519 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:622:21\n    │\n622 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:699:21\n    │\n699 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:715:17\n    │\n715 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":236,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 900.81µs (664.15µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.17ms (1.09ms CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 1982663)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2027395)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10316)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.01ms (1.58ms CPU time)\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 2.19ms (1.59ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16646969)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.80ms (2.54ms CPU time)\n\nRan 31 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 865685)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5458693)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4658224)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1899453)\n[PASS] test_callbackRefusesReplay() (gas: 3206630)\n[PASS] test_callbackRefusesStrangers() (gas: 1765431)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1944256)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1819674)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1775981)\n[PASS] test_donationsFeedThePot() (gas: 60211)\n[PASS] test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() (gas: 2137333)\n[PASS] test_firstVerdictLetterIsPostedOnceByTheNextPublicCall() (gas: 2177154)\n[PASS] test_fortyEntriesThenFull() (gas: 16415346)\n[PASS] test_hasNoAdminSurface() (gas: 148144)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10197787)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329981)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2434741)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4455448)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1785031)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 326339)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2845250)\n[PASS] test_nextJudgePostsTheFirstVerdictLetter() (gas: 3120064)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 4007583)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 688122)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5484519)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10342505)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9424181)\n[PASS] test_slotPricingAndLimits() (gas: 528133)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2010308)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2414689)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3363804)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 11.68ms (48.11ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 394895, ~: 400851)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 59914576)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397355)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372210)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391418)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 60019299)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 50104)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374142)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488480)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622692)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539382)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349204)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 9625146)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 10203883)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344293)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349778)\n[PASS] test_sellsNeverDecay() (gas: 322018)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359969)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 54.77ms (102.15ms CPU time)\n\nRan 7 test suites in 56.34ms (77.52ms CPU time): 75 tests passed, 0 failed, 0 skipped (75 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.announceFirstVerdict()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":287,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":504,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":745,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6890,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/high] uninitialized-state at src/MeatbagGame.sol:189: MeatbagGame._entries (src/MeatbagGame.sol#159) is never initialized. It is used in:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:335: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagGame.sol:480: MeatbagGame._sunset() (src/MeatbagGame.sol#480-495) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:335: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:396: MeatbagHook._recordVolume(uint256) (src/MeatbagHook.sol#396-404) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:363: MeatbagHook._distribute() (src/MeatbagHook.sol#363-394) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:145: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#145-151) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagGame.sol:378: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#378-386) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/HeartbeatTreasury.sol:34: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:366: Reentrancy in MeatbagGame.claim() (src/MeatbagGame.sol#366-375):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:291: Reentrancy in MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:454: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#454-463):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:291: Reentrancy in MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:303: Reentrancy in MeatbagGame.sunset() (src/MeatbagGame.sol#303-307):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:411: MeatbagGame.judgeBody(uint256).defs (src/MeatbagGame.sol#411) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:482: MeatbagGame._sunset().entrants (src/MeatbagGame.sol#482) is a local variable never initialized\n[medium/medium] unused-return at src/MeatbagHook.sol:313: MeatbagHook.redeemClaims() (src/MeatbagHook.sol#313-317) ignores return value by poolManager.unlock() (src/MeatbagHook.sol#315)\n[low/medium] missing-zero-check at src/MeatbagHerald.sol:43: MeatbagHerald.constructor(address).game_ (src/MeatbagHerald.sol#43) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:164: MeatbagGame.constructor(MeatbagHerald,address,address,address).intake_ (src/MeatbagGame.sol#164) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagHook.sol:103: MeatbagHook.constructor(IPoolManager,address).factory_ (src/MeatbagHook.sol#103) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:164: MeatbagGame.constructor(MeatbagHerald,address,address,address).imd_ (src/MeatbagGame.sol#164) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:454: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#454-463):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:366: Reentrancy in MeatbagGame.claim() (src/MeatbagGame.sol#366-375):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:303: Reentrancy in MeatbagGame.sunset() (src/MeatbagGame.sol#303-307):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:291: Reentrancy in MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:319: Reentrancy in MeatbagHook.unlockCallback(bytes) (src/MeatbagHook.sol#319-326):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:313: Reentrancy in MeatbagHook.redeemClaims() (src/MeatbagHook.sol#313-317):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:454: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#454-463):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:291: Reentrancy in MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298):\n[low/medium] reentrancy-events at src/HeartbeatTreasury.sol:34: Reentrancy in HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:366: Reentrancy in MeatbagGame.claim() (src/MeatbagGame.sol#366-375):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:303: Reentrancy in MeatbagGame.sunset() (src/MeatbagGame.sol#303-307):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:254: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#254-286):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:291: Reentrancy in MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298):\n[low/medium] timestamp at src/MeatbagGame.sol:378: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#378-386) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:242: MeatbagGame.hungJuryAt() (src/MeatbagGame.sol#242-248) uses timestamp for comparisons\n[low/medium] timestamp at src/HeartbeatTreasury.sol:34: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:161: MeatbagHook.beforeInitialize(address,PoolKey,uint160) (src/MeatbagHook.sol#161-170) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:468: MeatbagGame._announceFirstVerdict() (src/MeatbagGame.sol#468-473) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:444: MeatbagGame._recordPot() (src/MeatbagGame.sol#444-449) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:227: MeatbagGame.nextRoundToJudge() (src/MeatbagGame.sol#227-229) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:335: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:254: MeatbagGame.judge() (src/MeatbagGame.sol#254-286) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:145: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#145-151) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:396: MeatbagHook._recordVolume(uint256) (src/MeatbagHook.sol#396-404) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:363: MeatbagHook._distribute() (src/MeatbagHook.sol#363-394) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:291: MeatbagGame.declareHungJury() (src/MeatbagGame.sol#291-298) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":717,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/HeartbeatTreasury.sol:34: ETH transferred without address checks (5 places)\n[high] reentrancy-state-change at src/MeatbagGame.sol:277: Reentrancy: State change after external call\n[high] unsafe-casting at src/MeatbagHook.sol:241: Unsafe Casting of integers\n[low] costly-loop at src/MeatbagGame.sol:487: Costly operations inside loop\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once\n[low] large-numeric-literal at src/MeatbagGame.sol:270: Large Numeric Literal (11 places)\n[low] literal-instead-of-constant at src/MeatbagGame.sol:270: Literal Instead of Constant (14 places)\n[low] require-revert-in-loop at src/MeatbagGame.sol:202: Loop Contains `require`/`revert`\n[low] state-change-without-event at src/MeatbagGame.sol:311: State Change Without Event\n[low] state-no-address-check at src/MeatbagHook.sol:165: Address State Variable Set Without Checks\n[low] unchecked-return at src/MeatbagHook.sol:315: Unchecked Return\n[low] unused-error at src/MeatbagHook.sol:44: Unused Error\n[low] unused-public-function at src/MeatbagGame.sol:189: Public Function Not Used Internally (4 places)\n[low] unused-state-variable at src/MeatbagHook.sol:96: Unused State Variable","passed":true},{"durationMs":10509,"exitCode":0,"name":"proof e95ca1f14686","output":"Compiling 106 files with Solc 0.8.26\nSolc 0.8.26 finished in 9.87s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:497:5:\n    |\n497 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-a4e36057/Proof_e95ca1f14686.t.sol:FirstVerdictStipendProofTest\n[PASS] test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() (gas: 1416562)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.47ms (747.88µs CPU time)\n\nRan 1 test suite in 4.27ms (1.47ms 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":"38c6921b31fbf354f8a74304632de4f5918b43671229d790ecb7fbf4bc8babb6","verifiedTreeHash":"62ed79dedf236781b9ca2d3c3bd82b925336cfdb","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":34507,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.34s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:474:5:\n    |\n474 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:368:9\n    │\n368 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:160:40\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:160:57\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\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/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:200:39\n    │\n200 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:215:30\n    │\n215 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:448:9\n    │\n448 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:434:13\n    │\n434 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:256:17\n    │\n256 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:280:9\n    │\n280 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:276:25\n    │\n276 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:290:13\n    │\n290 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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 `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\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/MeatbagGame.sol:447:9\n    │\n447 │         emit HungJury(day, unsettledStreak);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\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/MeatbagGame.sol:471:9\n    │\n471 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:347:9\n    │\n347 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:339:20\n    │\n339 │         r.winner = uint8(index);\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[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:467:31\n    │\n467 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:461:13\n    │\n461 │             entrants += _rounds[roundDays[i]].count;\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/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:431:17\n    │\n431 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:470:21\n    │\n470 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:573:21\n    │\n573 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:650:21\n    │\n650 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:666:17\n    │\n666 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":193,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 664.78µs (841.18µs CPU time)\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 678.18µs (209.46µs CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16000166)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.34ms (929.89µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2014504)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2024927)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10294)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.85ms (1.65ms CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 2.76ms (783.10µs CPU time)\n\nRan 28 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 853609)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5452434)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4608044)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1896875)\n[PASS] test_callbackRefusesReplay() (gas: 3184107)\n[PASS] test_callbackRefusesStrangers() (gas: 1762853)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1941590)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1817030)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1773447)\n[PASS] test_donationsFeedThePot() (gas: 60189)\n[PASS] test_fortyEntriesThenFull() (gas: 16409758)\n[PASS] test_hasNoAdminSurface() (gas: 147814)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10104225)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329695)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2429497)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4405359)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1782519)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 321535)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2840050)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3988319)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687858)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5396977)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10254721)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9337519)\n[PASS] test_slotPricingAndLimits() (gas: 527605)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2002904)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2358354)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3353864)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 22.33ms (46.30ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 395977, ~: 400820)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 17566836)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397333)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372188)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391396)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 17671537)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49994)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374120)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488436)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622328)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539018)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349182)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 21867593)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 13714697)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344249)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349756)\n[PASS] test_sellsNeverDecay() (gas: 321996)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359947)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 24.86ms (61.62ms CPU time)\n\nRan 7 test suites in 25.87ms (54.49ms CPU time): 72 tests passed, 0 failed, 0 skipped (72 total tests)\n","passed":true},{"durationMs":99,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":283,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":481,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":696,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6666,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/medium] reentrancy-eth at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[high/high] uninitialized-state at src/MeatbagGame.sol:185: MeatbagGame._entries (src/MeatbagGame.sol#155) is never initialized. It is used in:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:335: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagHook.sol:335: MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/MeatbagGame.sol:457: MeatbagGame._sunset() (src/MeatbagGame.sol#457-472) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/MeatbagGame.sol:365: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#365-373) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:363: MeatbagHook._distribute() (src/MeatbagHook.sol#363-394) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MeatbagHook.sol:145: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#145-151) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/HeartbeatTreasury.sol:34: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:441: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#441-450):\n[medium/medium] reentrancy-no-eth at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:250: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#250-281):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:250: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#250-281):\n[medium/medium] reentrancy-no-eth at src/MeatbagGame.sol:250: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#250-281):\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:398: MeatbagGame.judgeBody(uint256).defs (src/MeatbagGame.sol#398) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MeatbagGame.sol:459: MeatbagGame._sunset().entrants (src/MeatbagGame.sol#459) is a local variable never initialized\n[medium/medium] unused-return at src/MeatbagHook.sol:313: MeatbagHook.redeemClaims() (src/MeatbagHook.sol#313-317) ignores return value by poolManager.unlock() (src/MeatbagHook.sol#315)\n[low/medium] missing-zero-check at src/MeatbagHerald.sol:43: MeatbagHerald.constructor(address).game_ (src/MeatbagHerald.sol#43) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:160: MeatbagGame.constructor(MeatbagHerald,address,address,address).imd_ (src/MeatbagGame.sol#160) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagGame.sol:160: MeatbagGame.constructor(MeatbagHerald,address,address,address).intake_ (src/MeatbagGame.sol#160) lacks a zero-check on :\n[low/medium] missing-zero-check at src/MeatbagHook.sol:103: MeatbagHook.constructor(IPoolManager,address).factory_ (src/MeatbagHook.sol#103) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:250: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#250-281):\n[low/medium] reentrancy-benign at src/MeatbagGame.sol:441: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#441-450):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:319: Reentrancy in MeatbagHook.unlockCallback(bytes) (src/MeatbagHook.sol#319-326):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:250: Reentrancy in MeatbagGame.judge() (src/MeatbagGame.sol#250-281):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:313: Reentrancy in MeatbagHook.redeemClaims() (src/MeatbagHook.sol#313-317):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[low/medium] reentrancy-events at src/MeatbagGame.sol:441: Reentrancy in MeatbagGame._hung(uint256,bool) (src/MeatbagGame.sol#441-450):\n[low/medium] reentrancy-events at src/HeartbeatTreasury.sol:34: Reentrancy in HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:363: Reentrancy in MeatbagHook._distribute() (src/MeatbagHook.sol#363-394):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[low/medium] reentrancy-events at src/MeatbagHook.sol:335: Reentrancy in MeatbagHook._splitAndSettle(bool,uint256,uint256) (src/MeatbagHook.sol#335-361):\n[low/medium] timestamp at src/MeatbagGame.sol:238: MeatbagGame.hungJuryAt() (src/MeatbagGame.sol#238-244) uses timestamp for comparisons\n[low/medium] timestamp at src/HeartbeatTreasury.sol:34: HeartbeatTreasury.fundNextRun() (src/HeartbeatTreasury.sol#34-45) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:161: MeatbagHook.beforeInitialize(address,PoolKey,uint160) (src/MeatbagHook.sol#161-170) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:250: MeatbagGame.judge() (src/MeatbagGame.sol#250-281) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:431: MeatbagGame._recordPot() (src/MeatbagGame.sol#431-436) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:365: MeatbagGame.claimSunset(uint256) (src/MeatbagGame.sol#365-373) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:286: MeatbagGame.declareHungJury() (src/MeatbagGame.sol#286-292) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagGame.sol:223: MeatbagGame.nextRoundToJudge() (src/MeatbagGame.sol#223-225) uses timestamp for comparisons\n[low/medium] timestamp at src/MeatbagHook.sol:145: MeatbagHook.buyFeeBps() (src/MeatbagHook.sol#145-151) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":743,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/HeartbeatTreasury.sol:34: ETH transferred without address checks (5 places)\n[high] reentrancy-state-change at src/MeatbagGame.sol:272: Reentrancy: State change after external call\n[high] unsafe-casting at src/MeatbagHook.sol:241: Unsafe Casting of integers\n[low] costly-loop at src/MeatbagGame.sol:464: Costly operations inside loop\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once\n[low] large-numeric-literal at src/MeatbagGame.sol:265: Large Numeric Literal (11 places)\n[low] literal-instead-of-constant at src/MeatbagGame.sol:265: Literal Instead of Constant (14 places)\n[low] require-revert-in-loop at src/MeatbagGame.sol:198: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/MeatbagHook.sol:165: Address State Variable Set Without Checks\n[low] unchecked-return at src/MeatbagHook.sol:315: Unchecked Return\n[low] unused-error at src/MeatbagHook.sol:44: Unused Error\n[low] unused-public-function at src/MeatbagGame.sol:185: Public Function Not Used Internally (4 places)\n[low] unused-state-variable at src/MeatbagHook.sol:96: Unused State Variable","passed":true},{"durationMs":10845,"exitCode":0,"name":"proof 27228564e0e5","output":"Compiling 106 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.17s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:474:5:\n    |\n474 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-01013feb/Proof_27228564e0e5.t.sol:StrandedPotProof\n[PASS] test_unrequestedRoundCanStillBeClearedSoThePotIsNotStrandedForever() (gas: 662917)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 984.17µs (294.55µs CPU time)\n\nRan 1 test suite in 3.81ms (984.17µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":11585,"exitCode":0,"name":"proof 383f9f9feda1","output":"2026-10-09T14:53:37.377130Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-fVTZjk/repo/test/imd-proof-01013feb/Proof_27228564e0e5.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.83s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:474:5:\n    |\n474 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 2 tests for test/imd-proof-0db6b9c4/Proof_383f9f9feda1.t.sol:PartialFillProofTest\n[PASS] test_exactInputBuyStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() (gas: 126890)\n[PASS] test_exactOutputSellStoppedByAPriceLimitPaysTwoPercentOfTheEthThatMoved() (gas: 112160)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.04ms (580.70µs CPU time)\n\nRan 1 test suite in 6.73ms (6.04ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"57437623a5df5fe3a3eddfea5ea5d6b91091f3d9e0b2aabc5a5c6c7abe84f246","verifiedTreeHash":"d2006418c9c0155c9dd7689fadf9c1da6fbdedc5","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":53227,"exitCode":0,"name":"build","output":"Compiling 121 files with Solc 0.8.26\nSolc 0.8.26 finished in 53.07s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:420:5:\n    |\n420 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:321:9\n    │\n321 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:45:38\n   │\n45 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:149:40\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:149:57\n    │\n149 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagHook.sol:98:44\n   │\n98 │     constructor(IPoolManager poolManager_, address factory_) {\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/MeatbagHook.sol:142:13\n    │\n142 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:189:39\n    │\n189 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:204:30\n    │\n204 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:398:9\n    │\n398 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:387:13\n    │\n387 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:224:17\n    │\n224 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:247:9\n    │\n247 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:243:25\n    │\n243 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:256:13\n    │\n256 │         if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:224:19\n    │\n224 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:228:30\n    │\n228 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:234:63\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:234:70\n    │\n234 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagGame.sol:397:9\n    │\n397 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:417:9\n    │\n417 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:300:9\n    │\n300 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:292:20\n    │\n292 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:336:13\n    │\n336 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:330:13\n    │\n330 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:331:13\n    │\n331 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:334:13\n    │\n334 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:353:26\n    │\n353 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:358:26\n    │\n358 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:363:26\n    │\n363 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:268:44\n    │\n268 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:324:9\n    │\n324 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:332:13\n    │\n332 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:366:18\n    │\n366 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:369:9\n    │\n369 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:256:34\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:256:69\n    │\n256 │             ethMoved = exactIn ? uint256(-params.amountSpecified) : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:260:45\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:53\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:74\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:82\n    │\n260 │             uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:263:32\n    │\n263 │             unspecifiedDelta = 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/MeatbagHook.sol:263:39\n    │\n263 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:295:9\n    │\n295 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:413:31\n    │\n413 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:407:13\n    │\n407 │             entrants += _rounds[roundDays[i]].count;\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/MeatbagHook.sol:304:9\n    │\n304 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:318:27\n    │\n318 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:319:30\n    │\n319 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/MeatbagGame.t.sol:92:30\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n92 │             issuedAt: uint64(block.timestamp),\n   │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:93:31\n   │\n56 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n   │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n   ‡\n93 │             expiresAt: uint64(block.timestamp + 1 days)\n   │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:387:17\n    │\n387 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:426:21\n    │\n426 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:466:21\n    │\n466 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:482:17\n    │\n482 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":837,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 532.87µs (359.18µs CPU time)\n\nRan 6 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_anyoneMayCallIt() (gas: 86427)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 91310)\n[PASS] test_hasNoAdminSurface() (gas: 49068)\n[PASS] test_onlyOncePerTwelveHours() (gas: 174324)\n[PASS] test_revertsWhenEmpty() (gas: 31992)\n[PASS] test_sendsWhatItHasWhenBelowTheCap() (gas: 85978)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 582.34µs (758.14µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 574.48µs (644.01µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2006995)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2017446)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10282)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.49ms (1.63ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 17593141)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.57ms (4.22ms CPU time)\n\nRan 22 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aVerdictResetsTheStreak() (gas: 5419517)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 1789793)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1888848)\n[PASS] test_callbackRefusesReplay() (gas: 3168519)\n[PASS] test_callbackRefusesStrangers() (gas: 1755136)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1933714)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1809135)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1765730)\n[PASS] test_donationsFeedThePot() (gas: 60094)\n[PASS] test_fortyEntriesThenFull() (gas: 16404912)\n[PASS] test_hasNoAdminSurface() (gas: 146994)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329381)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2419150)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 317043)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2826729)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3966862)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687496)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10195010)\n[PASS] test_slotPricingAndLimits() (gas: 527147)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 1991416)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2350206)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3338334)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 33.11ms (20.67ms CPU time)\n\nRan 16 tests for test/MeatbagHookEdge.t.sol:MeatbagHookEdgeTest\n[PASS] testFuzz_buyRateIsMonotoneAndBounded(uint32,uint32) (runs: 512, μ: 36697, ~: 36691)\n[PASS] testFuzz_splitNeverLosesAWeiAndPotGetsTheDust(uint96,uint32) (runs: 512, μ: 364695, ~: 363406)\n[PASS] test_aRejectingSwarmWalletNeverHaltsSwapsAndIsRetried() (gas: 798021)\n[PASS] test_buyThenSellRoundTripPaysTwoFeesAndLeavesNothingBehind() (gas: 625537)\n[PASS] test_claimsAccumulateAcrossSwapsUntilTheManagerCanPay() (gas: 42418241)\n[PASS] test_decayBoundaryOneSecondBeforeAndAtThirtyMinutes() (gas: 41285)\n[PASS] test_distributeWithNothingOwedIsANoOp() (gas: 27856)\n[PASS] test_dustBuyWithAZeroFeeStillSwapsAndCountsVolume() (gas: 261883)\n[PASS] test_launchSurplusOnExactOutputBuysGoesToThePot() (gas: 370411)\n[PASS] test_oneWeiSellExactOutPaysOneWeiFee() (gas: 222898)\n[PASS] test_rateIsTwentyFivePercentBeforeAnyPoolOpens() (gas: 38767372)\n[PASS] test_redeemClaimsRevertsWhileTheManagerCannotCoverThem() (gas: 42171022)\n[PASS] test_swapOnAnotherPoolThroughThisHookIsRefused() (gas: 102970)\n[PASS] test_unlockCallbackCannotBeDrivenByAStranger() (gas: 33370)\n[PASS] test_unusedCallbacksRevertEvenForTheManager() (gas: 52839)\n[PASS] test_volumeMilestoneLettersArePostedOnceEach() (gas: 853857)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 33.11ms (108.52ms CPU time)\n\nRan 18 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 398227, ~: 400714)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 42157387)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397192)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthThePoolTook() (gas: 383088)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77882)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 42262075)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 141981)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49858)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 372860)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541506)\n[PASS] test_hasNoOwnerOrSetters() (gas: 119991)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349115)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 22337363)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8929)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 33272566)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344198)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349481)\n[PASS] test_sellsNeverDecay() (gas: 321879)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 39.66ms (114.37ms CPU time)\n\nRan 19 tests for test/MeatbagGameEdge.t.sol:MeatbagGameEdgeTest\n[PASS] testFuzz_slotPricesSumToTheTriangle(uint8) (runs: 200, μ: 1777090, ~: 1227456)\n[PASS] test_anEighthHungRoundStartsANewStreakNotANewSunset() (gas: 9757047)\n[PASS] test_asciiBoundsAreInclusive() (gas: 455520)\n[PASS] test_attestationAtTheExactExpiryStillVerifies() (gas: 2025002)\n[PASS] test_carryOverCompoundsAcrossRounds() (gas: 3418004)\n[PASS] test_claimIsNotReenterable() (gas: 2505973)\n[PASS] test_dayBoundaryIsExact() (gas: 1905994)\n[PASS] test_fortyEntriesJudgeBodyFitsTheIntakeLimit() (gas: 36137062)\n[PASS] test_issuedAtWithinToleranceIsAccepted() (gas: 1996923)\n[PASS] test_judgeNeedsTheKeepersImd() (gas: 1989799)\n[PASS] test_keeperRewardIsThreePercentOfThePotAtRequestTime() (gas: 1739283)\n[PASS] test_malformedAnswerBytesDoNotSettleTheRound() (gas: 2067114)\n[PASS] test_seventhHungJuryFromTheOracleStillSunsetsThePot() (gas: 9085878)\n[PASS] test_signatureForAnotherChainIsRefused() (gas: 1797965)\n[PASS] test_sunsetClaimsRefuseStrangersAndUnsunsetRounds() (gas: 8924049)\n[PASS] test_sunsetSharesAreEqualPerEntryNotPerWallet() (gas: 9409187)\n[PASS] test_truncatedSignatureIsRefused() (gas: 1836868)\n[PASS] test_underpaymentAndZeroPaymentAreRefused() (gas: 76464)\n[PASS] test_wrongAnswerTypeIsAHungJury() (gas: 1993527)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 41.44ms (88.08ms CPU time)\n\nRan 1 test for test/invariant/TreasuryInvariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_heartbeatCapHolds\n[PASS] invariant_nextRunIsNeverBeforeTheLast\n[PASS] invariant_onlyRunsMoveEth\n TreasuryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------+-------+---------+----------╮\n| Contract        | Selector | Calls | Reverts | Discards |\n+=========================================================+\n| TreasuryHandler | feed     | 969   | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | fund     | 969   | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | warp     | 942   | 0       | 0        |\n╰-----------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 145.95ms (144.43ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantTokensOnlyTest\n[PASS]\nHookInvariantTokensOnlyTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantTokensOnlyTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+--------------+-------+---------+----------╮\n| Contract    | Selector     | Calls | Reverts | Discards |\n+=========================================================+\n| HookHandler | buyExactIn   | 186   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | buyExactOut  | 184   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | distribute   | 177   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | redeemClaims | 175   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | sellExactIn  | 192   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | sellExactOut | 187   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | warpMinutes  | 179   | 0       | 0        |\n╰-------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 259.34ms (251.43ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantEthSeededTest\n[PASS]\nHookInvariantEthSeededTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_noClaimsOnAFundedManager\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantEthSeededTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+--------------+-------+---------+----------╮\n| Contract    | Selector     | Calls | Reverts | Discards |\n+=========================================================+\n| HookHandler | buyExactIn   | 186   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | buyExactOut  | 184   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | distribute   | 177   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | redeemClaims | 175   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | sellExactIn  | 192   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | sellExactOut | 187   | 0       | 0        |\n|-------------+--------------+-------+---------+----------|\n| HookHandler | warpMinutes  | 179   | 0       | 0        |\n╰-------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 365.89ms (358.35ms CPU time)\n\nRan 1 test for test/invariant/GameInvariant.t.sol:GameInvariantTest\n[PASS]\nGameInvariantTest invariants:\n[PASS] invariant_balanceIsPotPlusClaims\n[PASS] invariant_claimsAreFullyAccountedFor\n[PASS] invariant_ethIsConserved\n[PASS] invariant_potBounds\n[PASS] invariant_roundsAreFinishedInOrderAndStayFinished\n[PASS] invariant_streakNeverReachesSunset\n GameInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| GameHandler | claim       | 389   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | claimSunset | 375   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | declareHung | 329   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | deliver     | 348   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | donate      | 341   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | enter       | 373   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | judge       | 340   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | warp        | 385   | 0       | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 686.40ms (683.58ms CPU time)\n\nRan 13 test suites in 691.72ms (1.61s CPU time): 99 tests passed, 0 failed, 0 skipped (99 total tests)\n","passed":true},{"durationMs":64,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":232,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":188,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":427,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":381,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":62,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":512,\"test/MeatbagGameEdge.t.sol\":452,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":291,\"test/MeatbagHookEdge.t.sol\":243,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/fork/MainnetForkFees.t.sol\":91,\"test/invariant/GameInvariant.t.sol\":281,\"test/invariant/HookInvariant.t.sol\":266,\"test/invariant/TreasuryInvariant.t.sol\":84,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":62},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a297e5ffdb8b7d446eaa1171ed4506a728812247f5618975aca7924b7bf66399","verifiedTreeHash":"616ffc61f003484b1c6f694be619d02812893edc","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":33078,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 32.92s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:474:5:\n    |\n474 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:368:9\n    │\n368 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:160:40\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:160:57\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\n    │                                                         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:200:39\n    │\n200 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:215:30\n    │\n215 │         _rounds[day].count = uint8(slot + 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[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\n   │                 ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:448:9\n    │\n448 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:434:13\n    │\n434 │             herald.announce(_H_POT_RECORD);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\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/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\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/MeatbagGame.sol:256:17\n    │\n256 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:280:9\n    │\n280 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:276:25\n    │\n276 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:290:13\n    │\n290 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\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/MeatbagGame.sol:447:9\n    │\n447 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:471:9\n    │\n471 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:347:9\n    │\n347 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:339:20\n    │\n339 │         r.winner = uint8(index);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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 `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:467:31\n    │\n467 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:461:13\n    │\n461 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:431:17\n    │\n431 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:470:21\n    │\n470 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:573:21\n    │\n573 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:650:21\n    │\n650 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:666:17\n    │\n666 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":173,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 663.91µs (846.57µs CPU time)\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 631.19µs (509.10µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 645.77µs (821.21µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2014504)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2024927)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10294)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.10ms (1.10ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16000166)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.50ms (1.16ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 395710, ~: 400820)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 17566836)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397333)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372188)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391396)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 17671537)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49994)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374120)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488436)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622328)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539018)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349182)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 21867593)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 13714697)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344249)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349756)\n[PASS] test_sellsNeverDecay() (gas: 321996)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359947)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 26.09ms (85.60ms CPU time)\n\nRan 28 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 853609)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5452434)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4608044)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1896875)\n[PASS] test_callbackRefusesReplay() (gas: 3184107)\n[PASS] test_callbackRefusesStrangers() (gas: 1762853)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1941590)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1817030)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1773447)\n[PASS] test_donationsFeedThePot() (gas: 60189)\n[PASS] test_fortyEntriesThenFull() (gas: 16409758)\n[PASS] test_hasNoAdminSurface() (gas: 147814)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10104225)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329695)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2429497)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4405359)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1782519)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 321535)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2840050)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3988319)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687858)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5396977)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10254721)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9337519)\n[PASS] test_slotPricingAndLimits() (gas: 527605)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2002904)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2358354)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3353864)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 26.16ms (34.76ms CPU time)\n\nRan 7 test suites in 26.96ms (56.79ms CPU time): 72 tests passed, 0 failed, 0 skipped (72 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":283,\"foundry.toml\":31,\"launch.json\":30,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":481,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":696,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a7a05c784f3171358e708f485269442964248b7fcb7d527bfbf242a7db08b885","verifiedTreeHash":"78937ab39638844556a0c24b6925af62c337d1f7","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":65969,"exitCode":0,"name":"build","output":"Compiling 121 files with Solc 0.8.26\nSolc 0.8.26 finished in 65.80s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:474:5:\n    |\n474 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\n   │                 ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:368:9\n    │\n368 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:160:57\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:160:40\n    │\n160 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:200:39\n    │\n200 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\n    │                                            ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:215:30\n    │\n215 │         _rounds[day].count = uint8(slot + 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/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:448:9\n    │\n448 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:434:13\n    │\n434 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:256:17\n    │\n256 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:280:9\n    │\n280 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:276:25\n    │\n276 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:290:13\n    │\n290 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagGame.sol:447:9\n    │\n447 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:471:9\n    │\n471 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:347:9\n    │\n347 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:339:20\n    │\n339 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:467:31\n    │\n467 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:461:13\n    │\n461 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:431:17\n    │\n431 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:470:21\n    │\n470 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:573:21\n    │\n573 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:650:21\n    │\n650 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:666:17\n    │\n666 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":776,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 648.64µs (1.14ms CPU time)\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 591.11µs (466.33µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 734.45µs (988.80µs CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16000166)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.69ms (1.21ms CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 2014504)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2024927)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10294)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 24.40ms (1.31ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 395291, ~: 400820)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 17566836)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397333)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372188)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391396)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 17671537)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 49994)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374120)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488436)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622328)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539018)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349182)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 21867593)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 13714697)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344249)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349756)\n[PASS] test_sellsNeverDecay() (gas: 321996)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359947)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 30.83ms (68.50ms CPU time)\n\nRan 20 tests for test/MeatbagHookEdge.t.sol:MeatbagHookEdgeTest\n[PASS] testFuzz_buyRateIsMonotoneAndBounded(uint32,uint32) (runs: 512, μ: 36694, ~: 36687)\n[PASS] testFuzz_exactOutputBuyPaysTheBuyRateOnTheWholeSpend(uint96,uint32) (runs: 256, μ: 406816, ~: 417333)\n[PASS] testFuzz_splitNeverLosesAWeiAndPotGetsTheDust(uint96,uint32) (runs: 512, μ: 367094, ~: 363648)\n[PASS] test_aRejectingSwarmWalletNeverHaltsSwapsAndIsRetried() (gas: 798855)\n[PASS] test_buyThenSellRoundTripPaysTwoFeesAndLeavesNothingBehind() (gas: 625714)\n[PASS] test_claimsAccumulateAcrossSwapsUntilTheManagerCanPay() (gas: 17827748)\n[PASS] test_decayBoundaryOneSecondBeforeAndAtThirtyMinutes() (gas: 41285)\n[PASS] test_distributeWithNothingOwedIsANoOp() (gas: 27922)\n[PASS] test_donationWhileClaimsArePendingIsNeitherLostNorCountedTwice() (gas: 17790731)\n[PASS] test_dustBuyWithAZeroFeeStillSwapsAndCountsVolume() (gas: 262017)\n[PASS] test_launchSurplusOnExactOutputBuysGoesToThePot() (gas: 371046)\n[PASS] test_oneWeiSellExactOutPaysOneWeiFee() (gas: 223112)\n[PASS] test_rateIsTwentyFivePercentBeforeAnyPoolOpens() (gas: 14220019)\n[PASS] test_redeemClaimsRevertsWhileTheManagerCannotCoverThem() (gas: 17580502)\n[PASS] test_refusedPartialFillLeavesNoTraceAndTheNextSwapIsClean() (gas: 566081)\n[PASS] test_refusedPartialFillSellKeepsTheSellersTokens() (gas: 155969)\n[PASS] test_swapOnAnotherPoolThroughThisHookIsRefused() (gas: 103036)\n[PASS] test_unlockCallbackCannotBeDrivenByAStranger() (gas: 33436)\n[PASS] test_unusedCallbacksRevertEvenForTheManager() (gas: 52839)\n[PASS] test_volumeMilestoneLettersArePostedOnceEach() (gas: 854144)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 34.76ms (129.94ms CPU time)\n\nRan 28 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 853609)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5452434)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4608044)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1896875)\n[PASS] test_callbackRefusesReplay() (gas: 3184107)\n[PASS] test_callbackRefusesStrangers() (gas: 1762853)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1941590)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1817030)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1773447)\n[PASS] test_donationsFeedThePot() (gas: 60189)\n[PASS] test_fortyEntriesThenFull() (gas: 16409758)\n[PASS] test_hasNoAdminSurface() (gas: 147814)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10104225)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329695)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2429497)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4405359)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1782519)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 321535)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2840050)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 3988319)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 687858)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5396977)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10254721)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9337519)\n[PASS] test_slotPricingAndLimits() (gas: 527605)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2002904)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2358354)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3353864)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 46.15ms (30.91ms CPU time)\n\nRan 25 tests for test/MeatbagGameEdge.t.sol:MeatbagGameEdgeTest\n[PASS] testFuzz_slotPricesSumToTheTriangle(uint8) (runs: 200, μ: 1704505, ~: 1090785)\n[PASS] test_anEighthHungRoundStartsANewStreakNotANewSunset() (gas: 9823351)\n[PASS] test_asciiBoundsAreInclusive() (gas: 455780)\n[PASS] test_attestationAtTheExactExpiryStillVerifies() (gas: 2032807)\n[PASS] test_carryOverCompoundsAcrossRounds() (gas: 3433457)\n[PASS] test_claimIsNotReenterable() (gas: 2513743)\n[PASS] test_dayBoundaryIsExact() (gas: 1913609)\n[PASS] test_entriesDuringADueSunsetAreNotPartOfTheSplit() (gas: 9172585)\n[PASS] test_fortyEntriesJudgeBodyFitsTheIntakeLimit() (gas: 36146733)\n[PASS] test_hungJuryAtFollowsTheRequestOnceJudged() (gas: 1715363)\n[PASS] test_issuedAtWithinToleranceIsAccepted() (gas: 2004682)\n[PASS] test_judgeNeedsTheKeepersImd() (gas: 2012430)\n[PASS] test_judgePullsThePriceOfTheMoment() (gas: 1717353)\n[PASS] test_judgeRefusedWhenTheKeeperCannotPayTheLivePrice() (gas: 436505)\n[PASS] test_keeperRewardIsThreePercentOfThePotAtRequestTime() (gas: 1746839)\n[PASS] test_malformedAnswerBytesDoNotSettleTheRound() (gas: 2075742)\n[PASS] test_seventhHungJuryFromTheOracleStillSunsetsThePot() (gas: 9011076)\n[PASS] test_signatureForAnotherChainIsRefused() (gas: 1805704)\n[PASS] test_sunsetClaimsRefuseStrangersAndUnsunsetRounds() (gas: 8982113)\n[PASS] test_sunsetRefusedUnlessDueAndOnlyOnce() (gas: 9035891)\n[PASS] test_sunsetSharesAreEqualPerEntryNotPerWallet() (gas: 9467713)\n[PASS] test_truncatedSignatureIsRefused() (gas: 1844749)\n[PASS] test_underpaymentAndZeroPaymentAreRefused() (gas: 76642)\n[PASS] test_unjudgedRoundHangsAtHungJuryAtExactlyNotBefore() (gas: 548540)\n[PASS] test_wrongAnswerTypeIsAHungJury() (gas: 2001164)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 46.22ms (91.65ms CPU time)\n\nRan 1 test for test/invariant/TreasuryInvariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_closingIsProportionalAndBounded\n[PASS] invariant_heartbeatCapHolds\n[PASS] invariant_onlyRunsMoveEth\n TreasuryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+-------------+-------+---------+----------╮\n| Contract        | Selector    | Calls | Reverts | Discards |\n+============================================================+\n| TreasuryHandler | feed        | 572   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | feedDust    | 579   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | fund        | 569   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | warp        | 592   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | warpSeconds | 568   | 0       | 0        |\n╰-----------------+-------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 150.19ms (148.67ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantTokensOnlyTest\n[PASS]\nHookInvariantTokensOnlyTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantTokensOnlyTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------------+-------+---------+----------╮\n| Contract    | Selector            | Calls | Reverts | Discards |\n+================================================================+\n| HookHandler | buyExactIn          | 97    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | buyExactOut         | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | distribute          | 107   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | donate              | 122   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactIn   | 102   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactOut  | 95    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactIn  | 112   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactOut | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | redeemClaims        | 114   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactIn         | 99    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactOut        | 107   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | warpMinutes         | 119   | 0       | 0        |\n╰-------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 320.27ms (310.69ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantEthSeededTest\n[PASS]\nHookInvariantEthSeededTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_noClaimsOnAFundedManager\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantEthSeededTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------------+-------+---------+----------╮\n| Contract    | Selector            | Calls | Reverts | Discards |\n+================================================================+\n| HookHandler | buyExactIn          | 97    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | buyExactOut         | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | distribute          | 107   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | donate              | 122   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactIn   | 102   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactOut  | 95    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactIn  | 112   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactOut | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | redeemClaims        | 114   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactIn         | 99    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactOut        | 107   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | warpMinutes         | 119   | 0       | 0        |\n╰-------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 447.27ms (438.62ms CPU time)\n\nRan 1 test for test/invariant/GameInvariant.t.sol:GameInvariantTest\n[PASS]\nGameInvariantTest invariants:\n[PASS] invariant_balanceIsPotPlusClaims\n[PASS] invariant_claimsAreFullyAccountedFor\n[PASS] invariant_ethIsConserved\n[PASS] invariant_potBounds\n[PASS] invariant_roundsAreFinishedInOrderAndStayFinished\n[PASS] invariant_streakNeverPassesSunset\n GameInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| GameHandler | claim       | 280   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | claimSunset | 265   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | declareHung | 284   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | deliver     | 240   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | donate      | 275   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | enter       | 259   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | judge       | 259   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | setPrice    | 264   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | setRefusing | 278   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | sunset      | 232   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | warp        | 244   | 0       | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 615.05ms (612.75ms CPU time)\n\nRan 13 test suites in 619.41ms (1.72s CPU time): 121 tests passed, 0 failed, 0 skipped (121 total tests)\n","passed":true},{"durationMs":71,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":283,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":481,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":696,\"test/MeatbagGameEdge.t.sol\":565,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagHookEdge.t.sol\":331,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/fork/MainnetForkFees.t.sol\":115,\"test/invariant/GameInvariant.t.sol\":360,\"test/invariant/HookInvariant.t.sol\":388,\"test/invariant/TreasuryInvariant.t.sol\":132,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a9e09452763e2aa9d25709443b2fbb287c5c317d5f19b3726a63bcf553d8d737","verifiedTreeHash":"c3708cbe1a41eea988b740721dae5532b350e060","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":34862,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.67s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:497:5:\n    |\n497 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:381:9\n    │\n381 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:164:57\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:164:40\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\n    │                                            ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:204:39\n    │\n204 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:219:30\n    │\n219 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:461:9\n    │\n461 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:447:13\n    │\n447 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:261:17\n    │\n261 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\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/MeatbagGame.sol:494:9\n    │\n494 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:460:9\n    │\n460 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:285:9\n    │\n285 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:281:25\n    │\n281 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:296:13\n    │\n296 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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 `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\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/MeatbagGame.sol:360:9\n    │\n360 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:352:20\n    │\n352 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `claimable` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\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/MeatbagGame.sol:372:9\n    │\n372 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:490:31\n    │\n490 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(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/MeatbagGame.sol:484:13\n    │\n484 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:480:17\n    │\n480 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:519:21\n    │\n519 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:622:21\n    │\n622 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:699:21\n    │\n699 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:715:17\n    │\n715 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":225,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 636.68µs (302.85µs CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 721.41µs (918.95µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 1982663)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2027395)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10316)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.70ms (1.55ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16646969)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.82ms (2.23ms CPU time)\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 3.72ms (1.09ms CPU time)\n\nRan 31 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 865685)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5458693)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4658224)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1899453)\n[PASS] test_callbackRefusesReplay() (gas: 3206630)\n[PASS] test_callbackRefusesStrangers() (gas: 1765431)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1944256)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1819674)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1775981)\n[PASS] test_donationsFeedThePot() (gas: 60211)\n[PASS] test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() (gas: 2137333)\n[PASS] test_firstVerdictLetterIsPostedOnceByTheNextPublicCall() (gas: 2177154)\n[PASS] test_fortyEntriesThenFull() (gas: 16415346)\n[PASS] test_hasNoAdminSurface() (gas: 148144)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10197787)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329981)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2434741)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4455448)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1785031)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 326339)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2845250)\n[PASS] test_nextJudgePostsTheFirstVerdictLetter() (gas: 3120064)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 4007583)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 688122)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5484519)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10342505)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9424181)\n[PASS] test_slotPricingAndLimits() (gas: 528133)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2010308)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2414689)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3363804)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 7.24ms (34.47ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 393648, ~: 400842)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 59914576)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397355)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372210)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391418)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 60019299)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 50104)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374142)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488480)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622692)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539382)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349204)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 9625146)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 10203883)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344293)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349778)\n[PASS] test_sellsNeverDecay() (gas: 322018)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359969)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 58.82ms (110.46ms CPU time)\n\nRan 7 test suites in 59.64ms (75.66ms CPU time): 75 tests passed, 0 failed, 0 skipped (75 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.announceFirstVerdict()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":287,\"foundry.toml\":31,\"launch.json\":30,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":504,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":745,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ac59c2525ca2024be729694128c9f05e125d932ce3f13f6c402b921bd801894c","verifiedTreeHash":"5a85572714082f93202b145aac8339b76e553f7a","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":66154,"exitCode":0,"name":"build","output":"Compiling 121 files with Solc 0.8.26\nSolc 0.8.26 finished in 65.99s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/MeatbagGame.sol:497:5:\n    |\n497 |     function _sanitise(string storage text) internal view returns (bytes memory out) {\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/MeatbagHerald.sol:43:17\n   │\n43 │     constructor(address game_) {\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/HeartbeatTreasury.sol:44:9\n   │\n44 │         emit HeartbeatFunded(SWARM, amount, block.timestamp);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `sunsetClaimed` is changed without an event but is used for access control\n    ╭▸ src/MeatbagGame.sol:381:9\n    │\n381 │         sunsetClaimed[day][msg.sender] = true;\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/MeatbagGame.sol:52:38\n   │\n52 │     bytes32 public constant ACTION = bytes32(\"oracle.request@oracle-1\");\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' 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/MeatbagGame.sol:164:40\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagGame.sol:164:57\n    │\n164 │     constructor(MeatbagHerald herald_, address intake_, address imd_, address signer_)\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/MeatbagHook.sol:103:44\n    │\n103 │     constructor(IPoolManager poolManager_, address factory_) {\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/MeatbagHook.sol:149:13\n    │\n149 │         if (elapsed >= DECAY_DURATION) return BASE_FEE_BPS;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/MeatbagGame.sol:204:39\n    │\n204 │             if (c < 0x20 || c > 0x7E) revert NotAscii();\n    │                                       ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:219:30\n    │\n219 │         _rounds[day].count = uint8(slot + 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[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_rounds` is updated\n    ╭▸ src/MeatbagGame.sol:461:9\n    │\n461 │         herald.announce(_H_FIRST_HUNG_JURY);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/MeatbagGame.sol:447:13\n    │\n447 │             herald.announce(_H_POT_RECORD);\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/MeatbagGame.sol:261:17\n    │\n261 │             if (block.timestamp < r.requestedAt + VERDICT_TIMEOUT) revert VerdictPending(day);\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/MeatbagGame.sol:494:9\n    │\n494 │         emit Sunset(roundDays[first], roundDays[cursor - 1], entrants, share);\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/MeatbagGame.sol:460:9\n    │\n460 │         emit HungJury(day, unsettledStreak);\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/MeatbagGame.sol:285:9\n    │\n285 │         emit Judging(day, intakeRequestId, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:281:25\n    │\n281 │         r.requestedAt = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/MeatbagGame.sol:296:13\n    │\n296 │         if (block.timestamp < hungJuryAt()) revert NotTimedOut(day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:231:19\n    │\n231 │             fee = uint256(-params.amountSpecified) * buyFeeBps() / 10_000;\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/MeatbagHook.sol:235:30\n    │\n235 │             uint256 amount = uint256(params.amountSpecified);\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/MeatbagHook.sol:241:63\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagHook.sol:241:70\n    │\n241 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/MeatbagGame.sol:360:9\n    │\n360 │         emit Verdict(day, index, winner, prize, a.panelJobId, a.agreed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `claims` is updated\n    ╭▸ src/MeatbagHook.sol:357:13\n    │\n357 │             poolManager.mint(address(this), 0, fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagGame.sol:352:20\n    │\n352 │         r.winner = uint8(index);\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[reentrancy-no-eth]: external call can be reentered before `claimable` is updated\n    ╭▸ src/MeatbagGame.sol:472:9\n    │\n472 │         herald.announce(_H_FIRST_VERDICT);\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/MeatbagGame.sol:372:9\n    │\n372 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:351:13\n    │\n351 │             poolManager.burn(address(this), 0, redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:352:13\n    │\n352 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee + redeemed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `owedPot` is updated\n    ╭▸ src/MeatbagHook.sol:355:13\n    │\n355 │             poolManager.take(CurrencyLibrary.ADDRESS_ZERO, address(this), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedTreasury` is updated\n    ╭▸ src/MeatbagHook.sol:377:26\n    │\n377 │             (bool ok,) = address(game).call{value: toPot}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:382:26\n    │\n382 │             (bool ok,) = address(treasury).call{value: toTreasury}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `owedSwarm` is updated\n    ╭▸ src/MeatbagHook.sol:387:26\n    │\n387 │             (bool ok,) = SWARM.call{value: toSwarm}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagHook.sol:288:44\n    │\n288 │         return (IHooks.afterSwap.selector, unspecifiedDelta);\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/MeatbagHook.sol:345:9\n    │\n345 │         emit FeeTaken(buy, ethMoved, fee, toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:353:13\n    │\n353 │             emit ClaimsRedeemed(redeemed);\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/MeatbagHook.sol:390:18\n    │\n390 │             else emit Distributed(toPot, toSwarm, toTreasury);\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/MeatbagHook.sol:393:9\n    │\n393 │         emit Distributed(toPot, 0, toTreasury);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:260:41\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:49\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:70\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:260:78\n    │\n260 │         uint256 poolEth = amount0 < 0 ? uint256(uint128(-amount0)) : uint256(uint128(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/MeatbagHook.sol:269:40\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:269:81\n    │\n269 │             uint256 priced = exactIn ? uint256(-params.amountSpecified) - fee : uint256(params.amountSpecified) + fee;\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/MeatbagHook.sol:278:32\n    │\n278 │             unspecifiedDelta = 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/MeatbagHook.sol:278:39\n    │\n278 │             unspecifiedDelta = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MeatbagHook.sol:283:32\n    │\n283 │             unspecifiedDelta = 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/MeatbagHook.sol:283:39\n    │\n283 │             unspecifiedDelta = 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[unused-return]: return value of an external call is not used\n    ╭▸ src/MeatbagHook.sol:315:9\n    │\n315 │         poolManager.unlock(\"\");\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/MeatbagHook.sol:324:9\n    │\n324 │         emit ClaimsRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagGame.sol:490:31\n    │\n490 │         uint256 distributed = share * entrants;\n    │                               ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MeatbagGame.sol:484:13\n    │\n484 │             entrants += _rounds[roundDays[i]].count;\n    │             ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:339:27\n    │\n339 │         uint256 toSwarm = base * SWARM_BPS / 10_000;\n    │                           ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/MeatbagHook.sol:340:30\n    │\n340 │         uint256 toTreasury = base * TREASURY_BPS / 10_000;\n    │                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HeartbeatTreasury.t.sol:29:84\n   │\n29 │         vm.expectRevert(abi.encodeWithSelector(HeartbeatTreasury.TooSoon.selector, block.timestamp + 12 hours));\n   │                                                                                    ━━━━━━━━━━━━━━━\n30 │         treasury.fundNextRun();\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:31:17\n   │\n31 │         vm.warp(block.timestamp + 12 hours - 1);\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/HeartbeatTreasury.t.sol:42:40\n   │\n42 │         assertEq(treasury.nextRunAt(), block.timestamp + 12 hours * 3 / 10, \"30% of a run closes 30% of 12 h\");\n   │                                        ━━━━━━━━━━━━━━━\n   ‡\n46 │         vm.warp(treasury.nextRunAt());\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/MeatbagGame.t.sol:105:30\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n105 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:106:31\n    │\n 69 │         vm.warp((game.today() + 1) * 1 days + 1 hours);\n    │         ────────────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n106 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\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/MeatbagGame.t.sol:480:17\n    │\n480 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:519:21\n    │\n519 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:622:21\n    │\n622 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:699:21\n    │\n699 │             vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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/MeatbagGame.t.sol:715:17\n    │\n715 │         vm.warp(block.timestamp + game.VERDICT_TIMEOUT());\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":799,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/MeatbagToken.t.sol:MeatbagTokenTest\n[PASS] test_hasNoMintOrOwner() (gas: 51829)\n[PASS] test_mintsTheWholeSupplyToTheDeployer() (gas: 45670)\n[PASS] test_transferMovesExactlyWhatItWasAsked() (gas: 70002)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 620.65µs (365.61µs CPU time)\n\nRan 7 tests for test/HeartbeatTreasury.t.sol:HeartbeatTreasuryTest\n[PASS] test_aDustRunCannotUseUpTheHeartbeatSlot() (gas: 194139)\n[PASS] test_anyoneMayCallIt() (gas: 108587)\n[PASS] test_capsEachRunAtOneHundredthOfAnEther() (gas: 113508)\n[PASS] test_hasNoAdminSurface() (gas: 49090)\n[PASS] test_onlyOncePerTwelveHours() (gas: 200976)\n[PASS] test_revertsWhenEmpty() (gas: 31971)\n[PASS] test_sendsWhatItHasWhenBelowTheCapAndClosesForAProportionalTime() (gas: 206995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 685.13µs (1.27ms CPU time)\n\nRan 6 tests for test/MeatbagHerald.t.sol:MeatbagHeraldTest\n[PASS] test_constructorPostedTheLaunchLetterVerbatim() (gas: 27921)\n[PASS] test_everyCodeHasText() (gas: 30770)\n[PASS] test_fixedMessagesAreFixedAndPostedOnce() (gas: 204903)\n[PASS] test_onlyTheSwarmPostsFreeText() (gas: 92274)\n[PASS] test_strangersCannotAnnounce() (gas: 30608)\n[PASS] test_unknownCodeReverts() (gas: 67994)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 984.08µs (700.00µs CPU time)\n\nRan 4 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_acceptsAFreshSignatureFromTheVectorKey() (gas: 1982663)\n[PASS] test_acceptsTheProtocolSignatureAsDelivered() (gas: 2027395)\n[PASS] test_callbackSelectorIsCanonical() (gas: 553)\n[PASS] test_digestMatchesTheProtocol() (gas: 10316)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.62ms (1.60ms CPU time)\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployFunctionMinesAMatchingAddressAndWiresTheProject() (gas: 16646969)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.40ms (3.01ms CPU time)\n\nRan 31 tests for test/MeatbagGame.t.sol:MeatbagGameTest\n[PASS] test_aRoundTheIntakeRefusesIsDeclaredHungAfterTheTimeoutSoThePotIsNeverStranded() (gas: 865685)\n[PASS] test_aVerdictResetsTheStreak() (gas: 5458693)\n[PASS] test_callbackRefusesASignatureForAnotherDomain() (gas: 4658224)\n[PASS] test_callbackRefusesExpiredAndFutureAttestations() (gas: 1899453)\n[PASS] test_callbackRefusesReplay() (gas: 3206630)\n[PASS] test_callbackRefusesStrangers() (gas: 1765431)\n[PASS] test_callbackRefusesTamperedAnswers() (gas: 1944256)\n[PASS] test_callbackRefusesTheWrongSigner() (gas: 1819674)\n[PASS] test_callbackRefusesUnknownRequests() (gas: 1775981)\n[PASS] test_donationsFeedThePot() (gas: 60211)\n[PASS] test_firstVerdictFitsTheStipendAfterTheKeeperClaimed() (gas: 2137333)\n[PASS] test_firstVerdictLetterIsPostedOnceByTheNextPublicCall() (gas: 2177154)\n[PASS] test_fortyEntriesThenFull() (gas: 16415346)\n[PASS] test_hasNoAdminSurface() (gas: 148144)\n[PASS] test_judgeAndDeclareHungJurySettleADueSunsetBeforeTouchingTheNextRound() (gas: 10197787)\n[PASS] test_judgeBodyNeutralisesQuotesAndBackslashes() (gas: 329981)\n[PASS] test_judgePaysTheIntakeAndRewardsTheKeeper() (gas: 2434741)\n[PASS] test_judgePriceFallsBackToHalfAnImdWhenTheIntakeHasNoPriceList() (gas: 4455448)\n[PASS] test_judgePullsTheIntakesCurrentPrice() (gas: 1785031)\n[PASS] test_judgeRefusesOpenRoundsAndEmptyQueues() (gas: 326339)\n[PASS] test_judgeSweepsATimedOutRoundOnItsWay() (gas: 2845250)\n[PASS] test_nextJudgePostsTheFirstVerdictLetter() (gas: 3120064)\n[PASS] test_potRecordIsAnnouncedOnlyWhenBeaten() (gas: 4007583)\n[PASS] test_rejectsWrongPaymentLongTextNonAsciiAndDuplicates() (gas: 688122)\n[PASS] test_sevenRefusedRoundsSunsetThePotToTheirEntrants() (gas: 5484519)\n[PASS] test_sevenUnsettledRoundsSunsetThePotToTheirEntrants() (gas: 10342505)\n[PASS] test_seventhHungVerdictFromTheOracleFitsTheStipendAndLeavesTheSunsetToAnyone() (gas: 9424181)\n[PASS] test_slotPricingAndLimits() (gas: 528133)\n[PASS] test_timedOutRequestBecomesAHungJuryAndThePotCarriesOver() (gas: 2010308)\n[PASS] test_verdictPaysEightyPercentAndCarriesTheRest() (gas: 2414689)\n[PASS] test_weakPanelOrBadIndexIsAHungJuryNotAVerdict() (gas: 3363804)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 40.35ms (33.86ms CPU time)\n\nRan 29 tests for test/MeatbagGameEdge.t.sol:MeatbagGameEdgeTest\n[PASS] testFuzz_slotPricesSumToTheTriangle(uint8) (runs: 200, μ: 1849698, ~: 1228814)\n[PASS] test_aRefusedClaimDoesNotPostTheLetter() (gas: 2181176)\n[PASS] test_aSecondVerdictDoesNotRepostTheLetter() (gas: 3640430)\n[PASS] test_anEighthHungRoundStartsANewStreakNotANewSunset() (gas: 9923021)\n[PASS] test_asciiBoundsAreInclusive() (gas: 455956)\n[PASS] test_attestationAtTheExactExpiryStillVerifies() (gas: 2001010)\n[PASS] test_carryOverCompoundsAcrossRounds() (gas: 3457540)\n[PASS] test_claimIsNotReenterable() (gas: 2547305)\n[PASS] test_dayBoundaryIsExact() (gas: 1916165)\n[PASS] test_declareHungJuryPostsAPendingFirstVerdictLetterBeforeHangingTheNextRound() (gas: 2450810)\n[PASS] test_entriesDuringADueSunsetAreNotPartOfTheSplit() (gas: 9258191)\n[PASS] test_fortyEntriesJudgeBodyFitsTheIntakeLimit() (gas: 36116696)\n[PASS] test_hungCallbacksNeverPostTheFirstVerdictLetter() (gas: 2052282)\n[PASS] test_hungJuryAtFollowsTheRequestOnceJudged() (gas: 1720211)\n[PASS] test_issuedAtWithinToleranceIsAccepted() (gas: 1972863)\n[PASS] test_judgeNeedsTheKeepersImd() (gas: 2019504)\n[PASS] test_judgePullsThePriceOfTheMoment() (gas: 1719777)\n[PASS] test_judgeRefusedWhenTheKeeperCannotPayTheLivePrice() (gas: 439061)\n[PASS] test_keeperRewardIsThreePercentOfThePotAtRequestTime() (gas: 1752802)\n[PASS] test_malformedAnswerBytesDoNotSettleTheRound() (gas: 2080678)\n[PASS] test_seventhHungJuryFromTheOracleStillSunsetsThePot() (gas: 9096792)\n[PASS] test_signatureForAnotherChainIsRefused() (gas: 1808216)\n[PASS] test_sunsetClaimsRefuseStrangersAndUnsunsetRounds() (gas: 9067785)\n[PASS] test_sunsetRefusedUnlessDueAndOnlyOnce() (gas: 9121512)\n[PASS] test_sunsetSharesAreEqualPerEntryNotPerWallet() (gas: 9563250)\n[PASS] test_truncatedSignatureIsRefused() (gas: 1847283)\n[PASS] test_underpaymentAndZeroPaymentAreRefused() (gas: 76774)\n[PASS] test_unjudgedRoundHangsAtHungJuryAtExactlyNotBefore() (gas: 562910)\n[PASS] test_wrongAnswerTypeIsAHungJury() (gas: 2003676)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 45.05ms (106.85ms CPU time)\n\nRan 23 tests for test/MeatbagHook.t.sol:MeatbagHookTest\n[PASS] testFuzz_feeIsAlwaysTwoPercentAfterDecayAndSplitsSumToIt(uint96,bool) (runs: 64, μ: 397401, ~: 400842)\n[PASS] test_anyoneRedeemsClaimsOnceTheManagerHoldsEth() (gas: 59914576)\n[PASS] test_buyExactInputPaysTwoPercentSplitExactly() (gas: 397355)\n[PASS] test_buyExactOutputAtLaunchPaysTwentyFivePercentOfTheSpendLikeAnExactInputBuy() (gas: 372210)\n[PASS] test_buyExactOutputPaysTwoPercentOfTheEthTheBuyerSpends() (gas: 391418)\n[PASS] test_buyFeeDecaysLinearlyFromTwentyFiveToTwoPercent() (gas: 77962)\n[PASS] test_buyOnAFreshManagerSeededWithTokensOnlyMintsAClaimThenRedeemsIt() (gas: 60019299)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 142025)\n[PASS] test_constructorDeployedTheProjectAndTheHeraldSpoke() (gas: 50104)\n[PASS] test_decayAppliesToExactOutputBuysToo() (gas: 374142)\n[PASS] test_ethSentStraightToTheHookReachesThePot() (gas: 488480)\n[PASS] test_exactInputBuyCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 622692)\n[PASS] test_exactOutputSellCutShortByItsPriceLimitIsRefusedNotOvercharged() (gas: 539382)\n[PASS] test_firstTradeAndVolumeMilestonesAreAnnounced() (gas: 1541839)\n[PASS] test_hasNoOwnerOrSetters() (gas: 120217)\n[PASS] test_launchBuySurplusGoesEntirelyToThePot() (gas: 349204)\n[PASS] test_onlyTheFactoryOpensThePool() (gas: 9625146)\n[PASS] test_permissionsMatchTheMinedAddress() (gas: 8973)\n[PASS] test_poolOpensOnlyOnceAndOnlyAgainstEth() (gas: 10203883)\n[PASS] test_sellExactInputTakesTwoPercentOfTheEthOut() (gas: 344293)\n[PASS] test_sellExactOutputDeliversExactlyAndGrossesUpTheFee() (gas: 349778)\n[PASS] test_sellsNeverDecay() (gas: 322018)\n[PASS] test_swapsWhereEthIsUnspecifiedChargeOnTheSettledEthEvenWhenCutShort() (gas: 359969)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 51.88ms (98.67ms CPU time)\n\nRan 20 tests for test/MeatbagHookEdge.t.sol:MeatbagHookEdgeTest\n[PASS] testFuzz_buyRateIsMonotoneAndBounded(uint32,uint32) (runs: 512, μ: 36700, ~: 36687)\n[PASS] testFuzz_exactOutputBuyPaysTheBuyRateOnTheWholeSpend(uint96,uint32) (runs: 256, μ: 407089, ~: 417355)\n[PASS] testFuzz_splitNeverLosesAWeiAndPotGetsTheDust(uint96,uint32) (runs: 512, μ: 367839, ~: 363692)\n[PASS] test_aRejectingSwarmWalletNeverHaltsSwapsAndIsRetried() (gas: 798899)\n[PASS] test_buyThenSellRoundTripPaysTwoFeesAndLeavesNothingBehind() (gas: 625758)\n[PASS] test_claimsAccumulateAcrossSwapsUntilTheManagerCanPay() (gas: 60175510)\n[PASS] test_decayBoundaryOneSecondBeforeAndAtThirtyMinutes() (gas: 41285)\n[PASS] test_distributeWithNothingOwedIsANoOp() (gas: 27922)\n[PASS] test_donationWhileClaimsArePendingIsNeitherLostNorCountedTwice() (gas: 60138515)\n[PASS] test_dustBuyWithAZeroFeeStillSwapsAndCountsVolume() (gas: 262039)\n[PASS] test_launchSurplusOnExactOutputBuysGoesToThePot() (gas: 371090)\n[PASS] test_oneWeiSellExactOutPaysOneWeiFee() (gas: 223134)\n[PASS] test_rateIsTwentyFivePercentBeforeAnyPoolOpens() (gas: 56504518)\n[PASS] test_redeemClaimsRevertsWhileTheManagerCannotCoverThem() (gas: 59928242)\n[PASS] test_refusedPartialFillLeavesNoTraceAndTheNextSwapIsClean() (gas: 566125)\n[PASS] test_refusedPartialFillSellKeepsTheSellersTokens() (gas: 155991)\n[PASS] test_swapOnAnotherPoolThroughThisHookIsRefused() (gas: 103036)\n[PASS] test_unlockCallbackCannotBeDrivenByAStranger() (gas: 33436)\n[PASS] test_unusedCallbacksRevertEvenForTheManager() (gas: 52839)\n[PASS] test_volumeMilestoneLettersArePostedOnceEach() (gas: 854144)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 52.47ms (205.40ms CPU time)\n\nRan 1 test for test/invariant/TreasuryInvariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_closingIsProportionalAndBounded\n[PASS] invariant_heartbeatCapHolds\n[PASS] invariant_onlyRunsMoveEth\n TreasuryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+-------------+-------+---------+----------╮\n| Contract        | Selector    | Calls | Reverts | Discards |\n+============================================================+\n| TreasuryHandler | feed        | 581   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | feedDust    | 597   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | fund        | 555   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | warp        | 543   | 0       | 0        |\n|-----------------+-------------+-------+---------+----------|\n| TreasuryHandler | warpSeconds | 604   | 0       | 0        |\n╰-----------------+-------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 173.81ms (171.55ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantTokensOnlyTest\n[PASS]\nHookInvariantTokensOnlyTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantTokensOnlyTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------------+-------+---------+----------╮\n| Contract    | Selector            | Calls | Reverts | Discards |\n+================================================================+\n| HookHandler | buyExactIn          | 120   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | buyExactOut         | 113   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | distribute          | 88    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | donate              | 115   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactIn   | 129   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactOut  | 86    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactIn  | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactOut | 116   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | redeemClaims        | 116   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactIn         | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactOut        | 92    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | warpMinutes         | 99    | 0       | 0        |\n╰-------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 328.43ms (313.88ms CPU time)\n\nRan 1 test for test/invariant/HookInvariant.t.sol:HookInvariantEthSeededTest\n[PASS]\nHookInvariantEthSeededTest invariants:\n[PASS] invariant_feesAreFullyAccountedFor\n[PASS] invariant_hookHoldsNothingItself\n[PASS] invariant_noClaimsOnAFundedManager\n[PASS] invariant_potIsBackedAndSupplyIsFixed\n[PASS] invariant_rateBounds\n[PASS] invariant_splitsAreExact\n[PASS] invariant_volumeAndMilestones\n HookInvariantEthSeededTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------------+-------+---------+----------╮\n| Contract    | Selector            | Calls | Reverts | Discards |\n+================================================================+\n| HookHandler | buyExactIn          | 120   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | buyExactOut         | 113   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | distribute          | 88    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | donate              | 115   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactIn   | 129   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedBuyExactOut  | 86    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactIn  | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | limitedSellExactOut | 116   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | redeemClaims        | 116   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactIn         | 103   | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | sellExactOut        | 92    | 0       | 0        |\n|-------------+---------------------+-------+---------+----------|\n| HookHandler | warpMinutes         | 99    | 0       | 0        |\n╰-------------+---------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 479.08ms (453.19ms CPU time)\n\nRan 1 test for test/invariant/GameInvariant.t.sol:GameInvariantTest\n[PASS]\nGameInvariantTest invariants:\n[PASS] invariant_balanceIsPotPlusClaims\n[PASS] invariant_claimsAreFullyAccountedFor\n[PASS] invariant_ethIsConserved\n[PASS] invariant_firstVerdictLetterFollowsASettledRound\n[PASS] invariant_potBounds\n[PASS] invariant_roundsAreFinishedInOrderAndStayFinished\n[PASS] invariant_streakNeverPassesSunset\n[PASS] invariant_validAnswersFitTheStipend\n GameInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-------------+-------------+-------+---------+----------╮\n| Contract    | Selector    | Calls | Reverts | Discards |\n+========================================================+\n| GameHandler | announce    | 267   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | claim       | 242   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | claimSunset | 264   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | declareHung | 231   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | deliver     | 212   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | donate      | 239   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | enter       | 209   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | judge       | 237   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | setPrice    | 236   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | setRefusing | 268   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | sunset      | 235   | 0       | 0        |\n|-------------+-------------+-------+---------+----------|\n| GameHandler | warp        | 240   | 0       | 0        |\n╰-------------+-------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 647.84ms (645.03ms CPU time)\n\nRan 13 test suites in 652.16ms (1.83s CPU time): 128 tests passed, 0 failed, 0 skipped (128 total tests)\n","passed":true},{"durationMs":64,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HeartbeatTreasury.fundNextRun()\",\"HeartbeatTreasury.receive()\",\"MeatbagGame.announceFirstVerdict()\",\"MeatbagGame.claim()\",\"MeatbagGame.claimSunset(uint256)\",\"MeatbagGame.declareHungJury()\",\"MeatbagGame.enter(string)\",\"MeatbagGame.judge()\",\"MeatbagGame.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"MeatbagGame.receive()\",\"MeatbagGame.sunset()\",\"MeatbagHerald.announce(uint8)\",\"MeatbagHerald.post(string)\",\"MeatbagHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MeatbagHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MeatbagHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MeatbagHook.distribute()\",\"MeatbagHook.receive()\",\"MeatbagHook.redeemClaims()\",\"MeatbagHook.unlockCallback(bytes)\",\"MeatbagToken.approve(address,uint256)\",\"MeatbagToken.transfer(address,uint256)\",\"MeatbagToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":287,\"foundry.toml\":31,\"remappings.txt\":5,\"script/Deploy.s.sol\":49,\"site/index.html\":193,\"src/HeartbeatTreasury.sol\":46,\"src/HookFlags.sol\":35,\"src/MeatbagGame.sol\":504,\"src/MeatbagHerald.sol\":95,\"src/MeatbagHook.sol\":405,\"src/MeatbagToken.sol\":16,\"src/OracleAttestation.sol\":221,\"test/Deploy.t.sol\":24,\"test/HeartbeatTreasury.t.sol\":85,\"test/HookTestBase.sol\":130,\"test/MeatbagGame.t.sol\":745,\"test/MeatbagGameEdge.t.sol\":633,\"test/MeatbagHerald.t.sol\":81,\"test/MeatbagHook.t.sol\":395,\"test/MeatbagHookEdge.t.sol\":331,\"test/MeatbagToken.t.sol\":38,\"test/OracleConsumerConformance.t.sol\":143,\"test/fork/MainnetFork.t.sol\":81,\"test/fork/MainnetForkFees.t.sol\":115,\"test/invariant/GameInvariant.t.sol\":392,\"test/invariant/HookInvariant.t.sol\":388,\"test/invariant/TreasuryInvariant.t.sol\":132,\"test/mocks/MockERC20.sol\":15,\"test/mocks/MockIntake.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f1efefa9ff9a58222ac1b08266e48e7b599ef8933fb429d9349dd73a202d6222","verifiedTreeHash":"b8d786ca729cd31b79f80d53c84b65b91a8d26e6","verifierVersion":"0.1.0+c4d32abc"}]}