{"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":"b11abebd-ba5c-4fae-b171-d8cd64557091","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"f93c1415e67571bca28a0aef301b96820bc273ff1618edfb9c4f94e8292edb06","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":"d90ac143231d639c8819c36792239da1d599e8ebe8298b3aae237d9746cfcf20","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":"d13fc23ec31fc8748598db83e5dac5e754a8d09072d939fc9010bf984ecaf978","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":"0979aec3cb96a5c94e809cc83f89066774abc2038830d61464f9a07653a1a6e7","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":"f00675d79c55ca0de1058901a523fe736541d44534c1f5ec52dc5970b6a687d3","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":"2b096d410f3e3a12626d86a62a97e6cbc99bb69a289c9a4bfab0a9facd44e302","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":"47423231212485ed66a3e86fe14477e2e8209c34dd4402596e8200791a914268","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":"24842f40b077560676f196f8f29b91b6066c92b7319e0a290159d97e4afa2306","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":"working"}],"objective":"pray (PRAY): selling is a prayer to the swarm. Buy freely; to sell cheaply file a plea, a panel of IMD agents judges it through the IMD oracle. Blessed sells pay 1%, unblessed pay 50%, and that sinners' tax goes to PRAY stakers. Launch kind univ4_hook on Ethereum mainnet, paired with FWA 0xa0df17b5ac76ababa36e1450e2cbcd18a620c845: the launch's standard token and pool with our hook, plus our plea registry and staking vault. Deploy in the launch, then host a site. No owner, admin, upgrade or pause.\n\nToken name: pray. Symbol: PRAY.\n\nSTART FROM EXISTING CODE: accepted bundle https://api.imd.fun/bundles/70515e16b0da47ef709886f1414bb2e96c25e94eac7940d9f4771b7fb7e37be8 from job 46c695fa-7061-4e9f-a06a-659e1f72ee3b (all checks passed; it stopped at manifest only for the missing pool price). Use it as the base: keep its hook, registry, IMD oracle integration (Intake, signer, EIP-712, plea rules, blessing, sleep) and router; change only FEES and add STAKING below.\n\nPool (launch.json): policy 34, paired with FWA, fee 12500, tickSpacing 60, opening cap 2,000,000 FWA (0.002 FWA per PRAY, both 18 decimals). initialPrice sqrtPriceX96: 3543191142285914205922034323 if PRAY is currency0, 1771595571142957102961017161607 if PRAY is currency1.\n\nWhen the hook acts: on every swap; beforeSwap reads hookData and checks the blessing on sells; the fee is taken in the swap.\n\nFEES (hook, on top of the pool fee; taken from the FWA side and kept in FWA, never sold for ETH)\n- Buys 1% (first 30 min after launch decays linearly 50% to 1%) and blessed sells 1%: 90% to the burn vault, 10% to 0xd01122bBfFd00fc96252c8b29867a5359a3bca13.\n- Unblessed sells 50%: 90% to the staking vault, 10% to 0xd01122bBfFd00fc96252c8b29867a5359a3bca13.\n- Burn vault: anyone calls buyback() once per 60 s, up to 50,000 FWA; swaps FWA for PRAY through the pool and burns it; caller gets 0.5%.\n- Who can change it: nobody; all rates are immutable constants.\n\nSTAKING\n- stake/unstake PRAY, no lock; rewards in FWA, claim any time.\n- accPerShare accumulator; stake and unstake settle first, so no rewards for tax that arrived before staking.\n- Tax arriving with zero stakers is held and streamed over 7 days once stakers exist.\n- Views: totalStaked, staked(user), pending(user), total paid, backlog.\n\nSITE\nBuy/sell PRAY via our router; plea form (approve 0.5 IMD, file, watch the panel vote live, sell with the blessing); stake/unstake/claim with pending FWA and APR; public feed of pleas, verdicts and votes; tax paid to stakers, PRAY burned; swarm awake/asleep countdown; buyback button. Dark, candle-lit, minimal.\n\nTESTS\nAll fee paths and exact splits; launch decay; staking accrual, no retroactive rewards, backlog streaming, claim/unstake, vault solvency; existing oracle and blessing tests still pass. Mainnet fork: buy, blessed and unblessed sell through the real PoolManager. Independent review.","parentJobId":null,"planHash":"f699ab9169818624500f74e07f788abc5bb44df44dacf2da092a9527ac016fe4","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"b11abebd-ba5c-4fae-b171-d8cd64557091","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1161-pray-symbol-pray"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51875","feedbackHash":"b971658069440a28636226ea8a533fe88dde4960b0da483ba5ed26f81ebe21a2","nodeKey":"audit_economics","submissionHash":"f93c1415e67571bca28a0aef301b96820bc273ff1618edfb9c4f94e8292edb06","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52124","feedbackHash":"bc8ed1bfcffacc02013baccd2d8f345a9a33f3a12d641023b99e39f5bb40124d","nodeKey":"audit_flow","submissionHash":"d90ac143231d639c8819c36792239da1d599e8ebe8298b3aae237d9746cfcf20","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52207","feedbackHash":"ba6bc3db68609c582e3f7334477231c2d7a9def0431aae294803c76e06ade3b5","nodeKey":"audit_judge","submissionHash":"d13fc23ec31fc8748598db83e5dac5e754a8d09072d939fc9010bf984ecaf978","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51884","feedbackHash":"eb96519f5cae9121f0a2b5b332c8b627e57387572b568d3aa6feec1a3a581807","nodeKey":"audit_math","submissionHash":"0979aec3cb96a5c94e809cc83f89066774abc2038830d61464f9a07653a1a6e7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51451","feedbackHash":"10bbbd9cc7d1ed9fde18485add9350c9db187c6b13ca064590391dd63f24e395","nodeKey":"audit_permissions","submissionHash":"f00675d79c55ca0de1058901a523fe736541d44534c1f5ec52dc5970b6a687d3","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51018","feedbackHash":"3f664113215fd8feb4d20d5002e2670a41d16b2eb7b73e630dd25b889588c6fa","nodeKey":"build_contract_project","submissionHash":"2b096d410f3e3a12626d86a62a97e6cbc99bb69a289c9a4bfab0a9facd44e302","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50966","feedbackHash":"d6cfa275f4744a22996afce0e65b84768f0e7b6d2cffbc8e676d89552894e937","nodeKey":"build_contract_project","submissionHash":"74020d92640d611a718bb2f0c138eb24b7413cbed781039f3b093d5bcb64e0c9","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51874","feedbackHash":"d950ae86d621d68e584226be99b669086c179214f19c8bf3eefcf6688ddfface","nodeKey":"manifest","submissionHash":"47423231212485ed66a3e86fe14477e2e8209c34dd4402596e8200791a914268","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51886","feedbackHash":"4bf45635eac9724558fd61ba9eb0b3384731118b60cb9f162a427ae5e1da5c26","nodeKey":"write_foundry_tests","submissionHash":"24842f40b077560676f196f8f29b91b6066c92b7319e0a290159d97e4afa2306","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"8cb27ec71bbe1d2834b2b76bb0e3c9510da01d0629099d0f87ee16e5f2181d49","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4aa8623bfb8d8e5e","findings":[],"hash":"0979aec3cb96a5c94e809cc83f89066774abc2038830d61464f9a07653a1a6e7","nodeId":"f8af6706-0fe7-42d0-8ef7-aef5b27e2c79","outcome":"completed","summary":"The review is complete. The findings file is written and validated, and the scratch directory is removed.\n\n**Result: no reproducible defects in the math area.** `.imd-findings.json` holds an empty findings list and 28 coverage rows, one for each of the 24 listed entry points plus four invariants, every row marked `holds` with its reason.\n\n**What I traced and probed**\n\n- **Hook fee math on all four paths.** Exact-input buys and exact-output sells take the fee as a beforeSwap specified delta. Exact-output buys and exact-input sells take it in afterSwap from the actual FWA delta. Gross-ups round up, exact-side fees round down by at most one wei, and the 50% rate can never flip the swap sign. A probe confirmed an exact-output buy at launch pays exactly twice the pool input. The partial-fill equality check held when an exact-output sell asked for more FWA than the pool had: everything reverted, no fee or stream state survived.\n- **Launch decay** is monotone, hits 100 bps at exactly 30 minutes, and the 90/10 split always sums to the fee.\n- **Buyback floor and budget.** The TWAP quote matched 95% of the real swap output when the window was quiet. On a launch-size single-sided pool at 0.002 FWA per PRAY, a full 50,000 FWA buyback cleared the floor, so the 10%-minimum budget rule and the floor do not jointly brick the vault. A pump inside the window postpones the buyback as the README says. The spend/reward split never exceeds the budget.\n- **Staking accumulator and stream.** No overflow at supply bounds with 1e36 precision. A 1-wei sole staker followed by a whale showed no retroactive rewards. The re-anchored linear stream drifts under two wei per update and releases fully at the end.\n- **Boundaries.** FWA leaves the PoolManager only with the manager as one side, which its rule permits. Callback payloads are hash-bound. Zero and extreme inputs are refused before state writes.\n\n**One correction during the run.** I briefly drafted a manifest finding, then confirmed the file I had read was `deployment-parameters.json`, a handoff document. No `launch.json` exists yet, which is expected before the manifest step, so that finding was withdrawn.\n\n**Not reached.** The site's APR display and the fork rehearsal were read but not executed. Economic manipulation of the TWAP on a thin pool is documented by the author and is outside the math area.","treeHash":null,"usage":{"cachedInputTokens":4045114,"inputTokens":674,"model":"claude-fable-5-1","outputTokens":76721,"runtime":"claude","turns":52,"wallClockMs":1301269}},{"artifacts":[],"attempt":1,"bundleHash":"8f5e18966b187fc33d24726da9d5f0474d5997c673b5ec55a5294bc4d1bf03c8","device":"dd3018ab6b18e7bc","findings":[{"description":"notifyReward credits the whole 90% stakers' share of an unblessed sell to accPerShare at the moment of the sell, pro rata to whoever is staked. The only guard is SameBlock (a stake cannot be withdrawn in the block it was added). A holder therefore stakes the PRAY they are not selling in block N, sells unblessed in block N+1, and claims and unstakes in block N+2, taking back their own tax in proportion to their stake. Large holders are exactly the sellers the 50% tax is meant to bite, and they are also the ones who can dominate the staking pool. The README records this as a known economic property mandated by the brief's accPerShare wording; it is reported here because it removes most of the incentive to file a plea for anyone with a large position, and because the fix space (a minimum stake age before a stake earns, a short unstake cooldown, or streaming every reward over a period) does not conflict with the brief's requirements of no lock and an accPerShare accumulator.","line":75,"path":"src/StakingVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\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 {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {Hook} from \"src/Hook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {Token} from \"src/Token.sol\";\nimport {PrayRouter} from \"src/PrayRouter.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\n\n/// Finding: the sinners' tax is credited to whoever is staked at the moment of the sell, with no\n/// stake age, cooldown or streaming. A holder who stakes the rest of their PRAY one block before an\n/// unblessed sell and leaves one block after takes back almost all of their own tax. Expected: a seller\n/// cannot recover a material share (here: under 10%) of the tax they just paid by staking around the\n/// sell. Actual: with 9.9M PRAY staked against 100k community stake, the seller recovers ~89% of the\n/// tax and pays an effective 5.4% instead of 50%.\n\ncontract FwaMock is ERC20 {\n    address public manager;\n\n    constructor() ERC20(\"FWA\", \"FWA\") {}\n\n    function setManager(address m) external {\n        manager = m;\n    }\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n\n    function _update(address from, address to, uint256 amount) internal override {\n        if (from != address(0) && to != address(0) && from != manager && to != manager) revert(\"InvalidTransfer\");\n        super._update(from, to, amount);\n    }\n}\n\ncontract Seeder is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function seed(PoolKey memory k, int256 liquidity) external {\n        manager.unlock(abi.encode(k, liquidity, msg.sender));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (PoolKey memory k, int256 liquidity, address payer) = abi.decode(data, (PoolKey, int256, address));\n        (BalanceDelta d,) = manager.modifyLiquidity(k, ModifyLiquidityParams(-887220, 887220, liquidity, 0), \"\");\n        _pay(k.currency0, d.amount0(), payer);\n        _pay(k.currency1, d.amount1(), payer);\n        return \"\";\n    }\n\n    function _pay(Currency c, int128 d, address payer) private {\n        if (d >= 0) return;\n        manager.sync(c);\n        require(IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), uint256(-int256(d))));\n        manager.settle();\n    }\n}\n\ncontract SelfStakeProof is Test {\n    address constant FWA = 0xa0Df17B5aC76ABaBA36E1450E2cbCd18A620C845;\n    IPoolManager manager;\n    Token token;\n    Hook hook;\n    PrayRouter router;\n    StakingVault staking;\n    PoolKey key;\n    address whale = makeAddr(\"whale\");\n    address carol = makeAddr(\"carol\");\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1_800_000_000);\n        vm.roll(30_000_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(FWA, address(new FwaMock()).code);\n        FwaMock(FWA).setManager(address(manager));\n        token = new Token();\n        bytes memory creation = abi.encodePacked(type(Hook).creationCode, abi.encode(manager, address(token), address(this)));\n        bytes32 h = keccak256(creation);\n        for (uint256 salt = 0; salt < 300_000; ++salt) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), h)))));\n            if (!HookFlags.matches(predicted, HookFlags.PRAY)) continue;\n            address deployed;\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(creation, 32), mload(creation), salt)\n            }\n            hook = Hook(deployed);\n            break;\n        }\n        router = hook.router();\n        staking = hook.staking();\n        key = PoolKey(\n            Currency.wrap(address(token) < FWA ? address(token) : FWA),\n            Currency.wrap(address(token) < FWA ? FWA : address(token)),\n            12500,\n            60,\n            IHooks(address(hook))\n        );\n        manager.initialize(key, 1 << 96);\n        Seeder seeder = new Seeder(manager);\n        FwaMock(FWA).mint(address(this), 100_000_000 ether);\n        token.approve(address(seeder), type(uint256).max);\n        IERC20(FWA).approve(address(seeder), type(uint256).max);\n        seeder.seed(key, 50_000_000 ether);\n        vm.warp(block.timestamp + 1800);\n        token.transfer(whale, 10_000_000 ether);\n        token.transfer(carol, 100_000 ether);\n        vm.startPrank(carol);\n        token.approve(address(staking), type(uint256).max);\n        staking.stake(100_000 ether);\n        vm.stopPrank();\n        vm.startPrank(whale);\n        token.approve(address(staking), type(uint256).max);\n        token.approve(address(router), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function testSellerCannotTakeBackTheirOwnTaxByStakingAroundTheSell() public {\n        // Block N: stake everything that is not about to be sold.\n        vm.prank(whale);\n        staking.stake(9_900_000 ether);\n        vm.roll(block.number + 1);\n        // Block N+1: the unblessed sell.\n        bool zeroForOne = address(token) < FWA;\n        uint160 lim = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(whale, whale);\n        (, uint256 received) = router.swap(PrayRouter.Trade(false, -100_000 ether, 100_000 ether, 1, lim, block.timestamp));\n        uint256 fee = hook.totalFees();\n        assertEq(fee, (received + fee) / 2, \"fixture: 50% sinners' tax charged\");\n        // Block N+2: leave with the tax.\n        vm.roll(block.number + 1);\n        vm.prank(whale);\n        uint256 recovered = staking.claim();\n        vm.prank(whale);\n        staking.unstake(9_900_000 ether);\n        assertLt(recovered * 10, fee, \"the seller recovered 10% or more of the tax they just paid\");\n    }\n}","reproduction":"Pool seeded 50M/50M full range at price 1, pool matured past the launch decay. Carol stakes 100,000 PRAY (the community). The whale holds 10,000,000 PRAY, stakes 9,900,000 in block N, sells 100,000 PRAY exact-input unblessed through PrayRouter in block N+1, claims and unstakes in block N+2. Measured: gross FWA 98,555.35, fee 49,277.68 (50%), recovered by the seller 43,906.41 FWA, effective fee 544 bps; Carol receives 443.50 FWA. Expected: a seller cannot recover a material share of the tax they just paid (the proof asserts under 10%). Actual: 89% recovered.","severity":"medium","title":"Sinners' tax flows straight back to a seller who stakes around their own sell (effective tax 5.4%, not 50%)"},{"description":"For any swap not initiated by the PRAY router, beforeSwap attributes the sell to tx.origin and uses that wallet's blessing to pick the 1% rate; afterSwap then consumes the blessing. The seller of the PRAY is whoever funded the swap, which need not be tx.origin. A dApp, aggregator, airdrop claimer or any contract a blessed wallet interacts with during its 15-minute window can sell the contract's own PRAY inside that transaction, paying 1% instead of 50%, and the visitor's blessing (bought with 0.5 IMD and a panel approval, limited to one per 24 hours) is spent. The uniswap-v4-security reference lists tx.origin authorization as a prohibited pattern for this reason. The README documents the behaviour as attribution rather than authorization, but the blessing is the authorization for the discounted rate, so the distinction does not hold. A fix that keeps foreign routers working is to treat every non-PrayRouter sell as unblessed (50% unless the swarm sleeps) and never consume a blessing for them; smart wallets then use the PRAY router, which the README already advises.","line":161,"path":"src/Hook.sol","reproduction":"Alice is blessed for 100 PRAY. A Dapp contract holds 100 PRAY and, in a function Alice calls, sells its 100 PRAY through a generic unlock-callback router (not PrayRouter) with empty hookData. Alice only sends the transaction (tx.origin = Alice); msg.sender of the swap is the Dapp's router and the PRAY comes from the Dapp. Measured: hook fee 99 bps on the Dapp's sell; registry.available(Alice) drops from 100e18 to 0. Expected: the Dapp's sell pays the 50% unblessed rate and Alice's blessing is untouched.","severity":"medium","title":"tx.origin blessing attribution lets any contract a blessed wallet calls sell its own PRAY at 1% and burn the wallet's blessing"},{"description":"beforeSwap sets blessed = true for every exact-output sell when available > 0, because the PRAY input is not known yet; afterSwap then calls registry.consume with the actual PRAY paid, which reverts BlessingUnavailable when it exceeds the remaining blessing. The whole swap fails rather than falling back to the unblessed rate as exact-input sells do. Routers and aggregators that quote exact output for a wallet with a small leftover blessing get an unexpected revert whose reason is wrapped by the PoolManager. This is documented in the README as the whole-swap blessing rule and the UI uses exact input; it is recorded here because the asymmetry with exact-input sells is a usable griefing surface against integrations, and the tests in test/HookEdge.t.sol and test/invariants/FeeInvariant.t.sol only assert that the revert is atomic.","line":167,"path":"src/Hook.sol","reproduction":"Mature pool. Bless Alice for 10 PRAY, then call PrayRouter.swap with buy=false, amountSpecified=+20 FWA (exact output), maxInput=100 PRAY. Expected (by analogy with exact input): the sell executes at the 50% rate and the 10 PRAY blessing stays intact. Actual: the swap reverts (BlessingUnavailable wrapped in HookCallFailed); state unchanged.","severity":"low","title":"Exact-output sell with any blessing is judged provisionally and reverts when the PRAY needed exceeds the blessing, instead of paying 50%"},{"description":"lastStakeBlock is per account and unstake checks it against the whole position, so adding one wei in block N prevents withdrawing any of a long-standing stake in block N. The README says only that 'a stake cannot be withdrawn in the block it was added'. Harmless for honest stakers, but worth stating precisely in the README and the site, since a griefer cannot cause it (only the staker's own stake sets the block). Covered by test/StakingEdge.t.sol testTopUpLocksTheWholePositionForOneBlock.","line":94,"path":"src/StakingVault.sol","reproduction":"Stake 100 PRAY in block N. In block N+1 stake 1 wei, then call unstake(1) or unstake(100e18) in the same block: both revert SameBlock. In block N+2 unstake(100e18 + 1) succeeds.","severity":"info","title":"A top-up locks a staker's entire position for one block, not just the new stake"},{"description":"spend = budget*200/201 and reward = spend/200 round down, so spend + reward can be up to a few wei below budget. A budget of balance + 1 therefore passes the balance check and succeeds. Nothing is overdrawn (the claims actually burned are spend + reward, which are covered), so this is a documentation nit: the README's 'budget must be at least a tenth of the available balance' and 'InvalidBuyback when the budget exceeds the balance' should say the check is on the FWA actually used. Covered by test/HookEdge.t.sol testBuybackRejectsBudgetAboveBalance, which asserts a ten-wei overshoot is refused.","line":48,"path":"src/BurnVault.sol","reproduction":"Vault holds B FWA claims (for example 4.5e18 after a 500 FWA buy at 1%). buyback(B + 1) succeeds; buyback(B + 10) reverts InvalidBuyback.","severity":"info","title":"buyback(budget) accepts a budget a few wei above the claim balance because the balance check is on spend + reward"}],"hash":"24842f40b077560676f196f8f29b91b6066c92b7319e0a290159d97e4afa2306","nodeId":"f5bf8001-da53-4172-b297-b202867c79f3","outcome":"completed","summary":"Work is complete. The implementation held up under adversarial testing; the two real weaknesses I found are economic and documented as findings rather than asserted as correct.\n\n**What I delivered (all under `test/`)**\n\n- `test/invariants/StakingInvariant.t.sol`: handler-driven invariants for the staking vault through the real hook and pool. Stakes, unstakes, claims, unblessed exact-input and exact-output sells, buys, time, block rolls, and donations of PRAY and of unannounced FWA claims. Checks claim conservation, solvency of pending plus backlog, PRAY custody, the held/stream state machine, and that stake and unstake never change what a user is owed.\n- `test/invariants/FeeInvariant.t.sol`: every fee path in all four swap shapes under all three rate classes, through the PRAY router and a foreign router with spoofed hookData, plus pleas, denials, abandoned pleas, expiry, buybacks, treasury redemption, liquidity changes and time. Checks the split identity against ghost sums computed from swap deltas, claim balances versus counters, backing of claims by manager FWA, supply falling only by burns, nothing stranded, fee rate bounds and monotonic decay, and blessings never exceeding their grant.\n- `test/HookEdge.t.sol`, `test/StakingEdge.t.sol`, `test/RegistryEdge.t.sol`: failure paths. Zero and int256 minimum amounts, fees above int128, callbacks without context, wrong pools, every wrong initialization parameter on a fresh hook, calls from inside a foreign unlock, malformed router trades, partial fills, buyback cooldown and budget boundaries, a TWAP-blocked buyback that clears once history catches up, outsiders moving vault claims, empty-pool swaps, one-wei stakes, dust streams, same-block top-ups, donations, giant rewards, every attestation field at its boundary, final plea states, blessing window boundaries.\n- `test/FeeMath.t.sol`: 1,000-run fuzz of exact fee arithmetic per shape and rate class, launch decay, blessing consumption across splits, and treasury redemption, in both token orderings.\n- `test/MainnetFork.t.sol`: fork rehearsal against the real PoolManager and real FWA, guarded by `PRAY_FORK_RPC_URL` and skipped without it. Buys in both shapes, blessed and unblessed sells, staking claim paid in real FWA, buyback, treasury redemption, and the real FWA transfer restriction.\n\n**Results**\n\n| Run | Outcome |\n|---|---|\n| `forge test` excluding scratch | 189 passed, 0 failed, 7 skipped (guarded fork) |\n| Guarded fork suite with RPC set | 7 passed |\n| Existing `fork/Mainnet.t.sol` profile | 3 passed |\n| Invariant handler calls | 1,920 per suite, 0 reverts |\n\n**Findings in `.imd-findings.json`**\n\n- Medium, with proof: a seller who stakes their remaining PRAY one block before an unblessed sell and leaves one block after recovers about 89% of the sinners' tax. Measured effective fee is 5.44% instead of 50%. The README acknowledges this as brief-mandated, but it removes the incentive to plead for exactly the holders the tax targets.\n- Medium, with proof: sells through any router other than the PRAY router are attributed to `tx.origin`. A contract a blessed wallet merely calls sold its own PRAY at 1% and consumed the visitor's blessing.\n- Low: an exact-output sell with any blessing reverts when the PRAY needed exceeds it, instead of paying 50% like exact input does.\n- Two informational notes on documentation precision (top-up locks the whole position for one block; the buyback balance check is on FWA used, not the budget).\n\nBoth proof files fail on the current code and import only `src/` and forge-std as required.\n\n**Limitations**\n\nThe hook's nested-swap guard cannot be exercised in Foundry because transient storage resets between top-level test calls, and the hook calls no untrusted code during a swap, so I left it untested rather than assert a non-reproducible path. The staking invariant's \"tax arrives with no stakers\" branch is reached at the start of each fuzz run but rarely mid-run; the dedicated unit tests cover it directly.","treeHash":"fc6df00bd3f3fd55df02502f4d7105834a836631","usage":{"cachedInputTokens":9500778,"inputTokens":1218,"model":"claude-fable-5-1","outputTokens":156877,"runtime":"claude","turns":91,"wallClockMs":4143364}},{"artifacts":[],"attempt":1,"bundleHash":"ab1d255ac88e2a60f540969077635e016d548fcbe0d4fb4ac37173f0cc68e99e","device":"30a6c1a419ef4f9c","findings":[],"hash":"2b096d410f3e3a12626d86a62a97e6cbc99bb69a289c9a4bfab0a9facd44e302","nodeId":"e595669f-0c77-4faa-ad0c-0ff1d72e6e6d","outcome":"completed","summary":"The revision is complete. All four findings were reproduced before anything changed, and the two reproducible ones are fixed in code.\n\n**What changed**\n\n- **Seller recapture** (`src/StakingVault.sol`). The hook now passes the attributed seller to the vault. Each sell's tax is distributed over every earning stake except the seller's own, with the seller settled first and its debt bumped past the credit. A stake also only starts earning in the block after it was added and cannot leave that block, so a same-block stake ahead of someone else's sell earns nothing from it. When nobody but the seller is earning, the tax is held and streamed over 7 days. In the reviewer's own scenario the seller now gets 0 FWA back and the effective sell fee is 5000 bps.\n- **Blessing attribution** (`src/Hook.sol`). The hook no longer reads tx.origin. Only the router's hookData is trusted. Any other unlocker is attributed to itself, pays the sinners' tax, and never consumes a blessing. A contract called by a blessed wallet now pays 50% on its own PRAY and the wallet's blessing stays intact.\n- **Exact-output sells** (`src/PrayRouter.sol`). The router passes its max-input bound in hookData, and an exact-output sell is blessed only when that bound fits the blessing. Over the blessing it pays 50% and keeps the blessing instead of reverting.\n- **Plea cap**. Documented as a filing-time snapshot in the README and on the site. No contract change.\n\n**The reviewer's proof is disputed, not ignored.** It fails on the fixed code only at its own line asserting Bob's pending equals 95% of the stakers' share. That assertion contradicts the final one: with a 50% fee, recovering 85.5% of it can never leave an effective fee of 40%. No code can pass the file as written. A copy with that single line removed passes. The responses file says exactly this and records the fix.\n\n**Verification run here**\n\n| Check | Result |\n|---|---|\n| forge build, forge fmt --check | pass |\n| forge test (deliverable suites) | 112 passed, 0 failed |\n| Protected launch floor from scratch | 11 passed |\n| Mainnet fork rehearsal | 3 passed |\n| Node checks (build, quote race, oracle) | pass |\n\nAnswers for all four findings are in `.imd-responses.json`. The README, review and validation records describe the new rules and the residual limit that identity-based exclusion cannot see a second wallet.","treeHash":"a9894c2c8cf99476027fa4172e67f3c820b31936","usage":{"cachedInputTokens":3366621,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":59760,"runtime":"claude","turns":64,"wallClockMs":1234782}},{"artifacts":[],"attempt":1,"bundleHash":"ae17eccb81463cf1546d3191de4f1d3d832c0411169f235cb9e7cda67bff55bf","device":"fb26479062458645","findings":[],"hash":"47423231212485ed66a3e86fe14477e2e8209c34dd4402596e8200791a914268","nodeId":"a3c0721f-914c-46c1-8b05-1031cc9f40b1","outcome":"completed","summary":"Only `launch.json` was created and the build and 98 tests pass. The work is done.\n\n**What I wrote.** `launch.json` describes the accepted PRAY tree without touching any other file.\n\n- **Hook:** contract `Hook`, constructor args `$poolManager`, `$token`, `$factory` in declaration order. There is no owner argument because the hook has no owner, matching the brief's no-admin rule.\n- **Permissions:** beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. These are exactly what `getHookPermissions` returns, and they sum to the address flags 8396 the repository mines for.\n- **Token:** contract `Token`, name pray, symbol PRAY, 18 decimals. It takes no constructor arguments.\n- **Pool:** paired currency FWA in lowercase, fee 12500, tickSpacing 60, initialPrice set to the policy's one-to-one placeholder as the reference's \"This launch\" section instructs. The notes record the brief's two sort-order-dependent opening prices for the 0.002 FWA per PRAY cap so the deployer has them.\n- **Notes:** 3271 characters, under the 4000 cap. They cover the fee schedule and splits, the claim-based settlement, the burn vault, staking, the plea registry's oracle constants, and the site handoff.\n\n**Verification.** The manifest parses with `kind` as its first key, every address is lowercase, fee, tickSpacing and decimals are numbers, and the price is a decimal string below 2^256. `forge build` succeeds and `forge test` reports 98 passed, 0 failed. Git shows the new manifest as the only change.\n\nOne judgment call to flag: the brief lists two explicit opening sqrtPriceX96 values, but the task rules say to write the initialPrice the reference gives, which is the one-to-one placeholder the deployer overrides from the opening cap. I followed the rules and put the brief's values in notes, since the hook accepts any valid price.","treeHash":"c2bae2d84c5b5377ecdac9b46e93400df9132ad7","usage":{"cachedInputTokens":404913,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":5277,"runtime":"claude","turns":13,"wallClockMs":186531}},{"artifacts":[],"attempt":1,"bundleHash":"356e25277cd2cdeffc3f8bf7ff41c09d55c66354232a6f78eba47cf7a149d41d","device":"759c614fdc84ff66","findings":[],"hash":"74020d92640d611a718bb2f0c138eb24b7413cbed781039f3b093d5bcb64e0c9","nodeId":"e595669f-0c77-4faa-ad0c-0ff1d72e6e6d","outcome":"completed","summary":"I've built PRAY on the accepted base bundle. As the brief asked, I changed only the fees and added staking. `forge build`, `forge test` (98 passed, 0 failed) and `forge fmt --check` all pass with the pinned solc 0.8.26. The mainnet fork rehearsal against the real PoolManager and real FWA passed 3 of 3: a buy, a blessed sell, and an unblessed sell with a staker who then claimed. I also ran the protected launch checks locally in `test/scratch/` against the built Hook bytecode, and all 11 passed, including the pool being opened by the factory. I haven't committed anything; the files are in the working tree, and there is no `launch.json`, which belongs to the manifest step.\n\n**Fees** (`src/Hook.sol`). All rates are fixed constants; there is no owner, admin, upgrade or pause. Fees are taken from the FWA side and kept as FWA, never sold for ETH. Every fee splits 90% to its destination and 10% to `0xd01122…3bca13`.\n\n| Swap | Hook fee | 90% goes to |\n|---|---|---|\n| Buy | 50%, falling linearly to 1% over the first 30 minutes | Burn vault |\n| Blessed sell | 1% | Burn vault |\n| Sell while the swarm is asleep (rule kept from the base) | 1% | Burn vault |\n| Unblessed sell | 50% | Staking vault |\n\n**Staking** (new `src/StakingVault.sol`, created by the hook's constructor and readable as `hook.staking()`):\n- You can stake and unstake PRAY at any time and claim FWA rewards at any time. Claims are paid out by the PoolManager, which FWA's transfer rules allow.\n- New stakers get no rewards from tax that arrived before they staked. Tax that arrives when nobody is staked is held and streamed over 7 days once someone stakes.\n- Rounding always favours the vault, and a fuzz test checks it never owes more than it holds.\n- **One deviation from \"no lock\":** you can't unstake in the same block you staked. Without this, someone could borrow PRAY inside a single PoolManager transaction, stake it around their own unblessed sell, and get most of the 50% tax back before repaying.\n\n**Buyback:** I added a no-argument `buyback()` that spends the whole vault balance, up to 50,000 FWA. Each call must now also spend at least a tenth of what's available, so nobody can keep resetting the 60-second cooldown with tiny buybacks. The review found that problem.\n\n**Site:** I added stake, unstake and claim, with your pending FWA, total staked, backlog and an estimated APR. It also shows tax paid to stakers and PRAY burned. Plea filing, live panel votes, the activity feed, the awake/asleep countdown and the buyback button are kept from the base.\n\n**Review:** a separate review pass, which didn't write the code, read the hook and both vaults. I fixed the buyback griefing, a duplicate read of the sleep status and a misleading event field name. Findings and decisions are in `docs/REVIEW.md`; this is not an external audit.\n\nRemaining risks, documented in the README:\n- **Just-in-time staking.** While anyone is staked, the sinners' tax is shared out immediately, as the brief specifies. So a large holder who stakes at least one block before an unblessed sell takes a matching share of it. That includes the seller staking from a second wallet.\n- **Kept from the base:**\n  - A buyback can be sandwiched for up to about 5% of its size.\n  - An exact-output sell bigger than the remaining blessing reverts instead of paying 50%.\n  - Sales that don't go through the PRAY router credit the blessing to `tx.origin`.\n\nThe launch parameters you gave are in the README and `deployment-parameters.json`: policy 34, fee 12500, tick spacing 60, and both opening prices depending on token order. There is nothing to set after launch.","treeHash":"c2f0bbf0b5218ac51718a8d2e4da86ce1b0f43dc","usage":{"cachedInputTokens":4719082,"inputTokens":86,"model":"claude-opus-5-5","outputTokens":55439,"runtime":"claude","turns":47,"wallClockMs":1525961}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4895726008c69d01","findings":[],"hash":"7608982f80a21af506d15499bcd8b44e12ad8d5e53226536e3281ff1366fbfa3","nodeId":"a3c0721f-914c-46c1-8b05-1031cc9f40b1","outcome":"failed","summary":"needs input: The one-to-one pool.initialPrice is 500 times the requested 0.002 FWA per PRAY. The correct replacement depends on the production PRAY token's address ordering against FWA. Neither the supplied working tree nor accepted bundle provides that address or the token's CREATE2 deployment inputs. The schema requires one decimal price string and provides no order-dependent price placeholder. Choosing either supplied price without the ordering would be a guess, which the assignment forbids. — What is the launch's deterministic PRAY token address, or its confirmed currency0/currency1 ordering against FWA at 0xa0df17b5ac76ababa36e1450e2cbcd18a620c845?","treeHash":null,"usage":{"cachedInputTokens":1628672,"inputTokens":65954,"model":"gpt-6-astra","outputTokens":10384,"runtime":"codex","turns":8,"wallClockMs":510834}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f009de0ce52c7b3a","findings":[{"citation":"resolved","description":"The brief fixes the opening price (2,000,000 FWA cap, 0.002 FWA per PRAY: sqrtPriceX96 3543191142285914205922034323 if PRAY is currency0, 1771595571142957102961017161607 if PRAY is currency1) and says the accepted base stopped at the manifest only because that price was missing. The manifest written now carries the one-to-one placeholder 2^96 (1 FWA per PRAY) and pushes the real value into `notes` (\"the deployer derives the opening price from the 2,000,000 FWA opening cap\"). Notes are explanatory text, not deployment authority; pool.initialPrice is the sqrtPriceX96 the factory passes to PoolManager.initialize and the deployer checks the pool fields against the signed manifest. Hook.beforeInitialize (src/Hook.sol:112-131) accepts any price and sets `initialized = true` once, so the pool opens at 1 FWA per PRAY (1,000,000,000 FWA fully diluted instead of 2,000,000), every seeded PRAY is offered at 500x the agreed price, and nothing can correct it afterwards: the hook refuses a second initialization and binds all swaps to that pool id. deployment-parameters.json lines 26-29 and README line 116 both record the right numbers; only the manifest, the file that is deployed from, is wrong. Fix: write the brief's value for the launch's actual token ordering into pool.initialPrice (one decimal string; the ordering follows from the token's CREATE2 address, which the manifest step must resolve), and optionally make beforeInitialize reject any other sqrtPriceX96 for the pair ordering it sees so a mispriced manifest fails to launch instead of opening a mispriced pool.","line":28,"path":"launch.json","reproduction":"Read launch.json line 28: initialPrice is \"79228162514264337593543950336\" (= 2^96, price 1.0). Expected per the brief: \"3543191142285914205922034323\" (PRAY currency0) or \"1771595571142957102961017161607\" (PRAY currency1). In Foundry: deploy Hook as in test/TestBase.sol, call manager.initialize(key, 79228162514264337593543950336) from the factory address: beforeInitialize returns its selector, hook.initialized() == true, launchedAt set; manager.getSlot0(poolId).sqrtPriceX96 == 2^96, i.e. 1 FWA per PRAY; a second manager.initialize(key, 3543191142285914205922034323) reverts InvalidPool (initialized already true). A 1 PRAY buy through PrayRouter then costs about 1.0 FWA plus fees instead of about 0.002 FWA.","severity":"high","snippet":"    \"initialPrice\": \"79228162514264337593543950336\"","title":"launch.json opens the pool at the one-to-one placeholder price, 500x the brief's 0.002 FWA per PRAY, and the hook can never be re-initialized"},{"citation":"resolved","description":"Hook._collect routes 90% of every unblessed sell's 50% fee to StakingVault.notifyReward inside the same swap (src/Hook.sol:244 `staking.notifyReward(vaultPart);`), and notifyReward credits it to accPerShare pro rata to the stake present at that moment. Nothing excludes the seller, nothing weights by time, claim() has no block guard, and the only guard (SameBlock on unstake, line 94) stops a stake from leaving in the block it was added. So a holder stakes the PRAY they are not selling in block N, sells unblessed in block N+1, claims in the same block and unstakes in block N+2: they get back stake/(stake+others) of 90% of their own tax. With a dominant stake the 50% sinners' tax collapses to about 5% of gross (the treasury's 10% share plus rounding), with no plea, no 0.5 IMD, no panel and no 24-hour cooldown; a searcher can do the same around other people's unblessed sells (stake in the same block before the victim's sell, claim after it, unstake next block) and take the tax from resident stakers; and tax that arrives while nobody is staked is streamed in full to whoever stakes first, even 1 wei. The brief's guarantees 'unblessed pay 50%' and 'that sinners' tax goes to PRAY stakers' therefore hold only for sellers who hold a small share of the stake, which is the opposite of the large holders the tax is meant to bind. README line 77 records this as a known property of immediate accPerShare distribution. Reported by all four specialists (write_foundry_tests, audit_permissions, audit_economics, audit_flow); merged here. It is a design decision for the requester: the brief's accPerShare, settle-first and no-lock requirements can still be met while damping it, e.g. distribute each sell's tax over totalStaked minus the seller's own stake (settle the seller first and bump their debt past it), and/or stream every tax receipt over a period the way the held backlog already is so a stake must stay exposed through the stream.","line":75,"path":"src/StakingVault.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 {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {Hook} from \"src/Hook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {Token} from \"src/Token.sol\";\nimport {PrayRouter} from \"src/PrayRouter.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\n\n/// @dev Stand-in for FWA: any address may mint, transfers unrestricted (the hook never transfers FWA\n/// wallet-to-wallet, so the restriction is irrelevant to this scenario).\ncontract ScratchFWA is ERC20 {\n    constructor() ERC20(\"Fake World Assets\", \"FWA\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice An unblessed seller who stakes PRAY one block ahead recaptures almost the whole\n/// sinners' tax: the brief's 50% sell fee collapses to ~5% (the treasury's 10% share) for any\n/// holder with a dominant share of the stake, with no plea and no panel.\ncontract SinnerSelfStakeTest is Test, IUnlockCallback {\n    address internal constant FWA = 0xa0Df17B5aC76ABaBA36E1450E2cbCd18A620C845;\n\n    IPoolManager internal manager;\n    Token internal token;\n    Hook internal hook;\n    PrayRouter internal router;\n    StakingVault internal staking;\n    PoolKey internal key;\n    address internal bob;\n    address internal carol;\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1_800_000_000);\n        vm.roll(30_000_000);\n        bob = makeAddr(\"bob\");\n        carol = makeAddr(\"carol\");\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(FWA, address(new ScratchFWA()).code);\n        token = new Token();\n\n        bytes memory creation =\n            abi.encodePacked(type(Hook).creationCode, abi.encode(manager, address(token), address(this)));\n        bytes32 hash = keccak256(creation);\n        for (uint256 salt = 0; salt < 200_000; ++salt) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.PRAY)) continue;\n            address deployed;\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(creation, 32), mload(creation), salt)\n            }\n            require(deployed == predicted, \"deploy failed\");\n            hook = Hook(deployed);\n            break;\n        }\n        require(address(hook) != address(0), \"mine failed\");\n        router = hook.router();\n        staking = hook.staking();\n\n        bool pray0 = address(token) < FWA;\n        key = PoolKey(\n            Currency.wrap(pray0 ? address(token) : FWA),\n            Currency.wrap(pray0 ? FWA : address(token)),\n            12500,\n            60,\n            IHooks(address(hook))\n        );\n        manager.initialize(key, 1 << 96);\n\n        ScratchFWA(FWA).mint(address(this), 100_000_000 ether);\n        manager.unlock(abi.encode(ModifyLiquidityParams(-887220, 887220, 50_000_000 ether, bytes32(0))));\n\n        token.transfer(bob, 2_000_000 ether);\n        token.transfer(carol, 100_000 ether);\n        vm.startPrank(bob);\n        token.approve(address(router), type(uint256).max);\n        token.approve(address(staking), type(uint256).max);\n        vm.stopPrank();\n        vm.prank(carol);\n        token.approve(address(staking), type(uint256).max);\n\n        // Past the launch decay; the swarm is awake, so an unblessed sell is a sinner's sell.\n        vm.warp(hook.launchedAt() + 1800);\n        assertFalse(hook.registry().sleeping());\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (BalanceDelta d,) = manager.modifyLiquidity(key, abi.decode(data, (ModifyLiquidityParams)), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 d) private {\n        if (d < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(-int256(d)));\n            manager.settle();\n        }\n    }\n\n    function _sell(address who, uint256 amount) private returns (uint256 received) {\n        bool zeroForOne = Currency.unwrap(key.currency0) == address(token);\n        uint160 lim = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(who, who);\n        (, received) = router.swap(PrayRouter.Trade(false, -int256(amount), amount, 1, lim, block.timestamp));\n    }\n\n    function testSinnerRecapturesOwnTaxByStakingOneBlockAhead() public {\n        // Carol is an honest staker with 100,000 PRAY.\n        vm.prank(carol);\n        staking.stake(100_000 ether);\n\n        // Bob holds 2,000,000 PRAY, has no blessing, and wants to dump 100,000 PRAY.\n        // Block N: he stakes the 1,900,000 PRAY he is not selling.\n        vm.prank(bob);\n        staking.stake(1_900_000 ether);\n        vm.roll(block.number + 1);\n        vm.warp(block.timestamp + 12);\n\n        // Block N+1: unblessed exact-input sell of 100,000 PRAY.\n        uint256 received = _sell(bob, 100_000 ether);\n        uint256 fee = hook.totalFees();\n        uint256 gross = received + fee;\n        assertApproxEqAbs(fee, gross / 2, 1, \"hook charged 50% of gross\");\n        assertEq(hook.stakingFees(), fee - fee / 10, \"90% of the tax went to the staking vault\");\n\n        // Bob immediately holds 95% of the stakers' share as claimable FWA.\n        uint256 bobPending = staking.pending(bob);\n        assertApproxEqAbs(bobPending, (fee - fee / 10) * 19 / 20, 2, \"bob's pro-rata share\");\n\n        vm.prank(bob);\n        uint256 claimed = staking.claim();\n        assertEq(claimed, bobPending);\n\n        // Block N+2: Bob leaves. No lock applies.\n        vm.roll(block.number + 1);\n        vm.prank(bob);\n        staking.unstake(1_900_000 ether);\n\n        // Bob's effective hook fee on an *unblessed* sell: what he paid minus what the tax\n        // handed straight back to him.\n        uint256 effective = fee - claimed;\n        uint256 effectiveBps = effective * 10_000 / gross;\n        emit log_named_uint(\"gross FWA proceeds\", gross);\n        emit log_named_uint(\"hook fee charged\", fee);\n        emit log_named_uint(\"tax returned to seller\", claimed);\n        emit log_named_uint(\"effective sell fee bps\", effectiveBps);\n\n        // The brief: unblessed sells pay 50%. A sinner who stakes pays ~7% here, and ~5% as the\n        // sole staker. This assertion fails on the current code and passes once the seller cannot\n        // collect its own tax (e.g. the tax is streamed over time, or the seller's own stake is\n        // excluded from the distribution of its own sell).\n        assertGe(effectiveBps, 4_000, \"an unblessed seller must not recapture its own sinners' tax\");\n    }\n}","reproduction":"State: pool seeded 50M/50M full range at price 1, warp to launchedAt + 1800 (swarm awake, buys at 1%). Carol stakes 100,000 PRAY. Bob holds 2,000,000 PRAY, no blessing. Block N: bob.stake(1_900_000e18). Block N+1: bob sells 100,000 PRAY exact-input through PrayRouter.swap(Trade(false, -100000e18, 100000e18, 1, limit, deadline)). Expected per brief: Bob pays 50% of gross FWA to stakers other than himself. Actual (test/scratch/P1.t.sol, from .imd/reads/proofs/Proof_a9cd6cc60edb.t.sol, run here): gross 98,555.35 FWA, hook fee 49,277.68 FWA (50%), staking.pending(bob) == 42,132.41 FWA at once (95% of the 90% stakers' share), bob.claim() pays it, block N+2 bob.unstake(1_900_000e18) succeeds; net fee 7,145.27 FWA = 7.25% of gross (5.04% when Bob holds 99% of the stake, per Proof_e42b0b5fe5c5: whale pending 44,305.156 FWA of 44,349.909 routed). Backlog variant: with no stakers the same sell parks 44,349.9 FWA in held(); whale.stake(1) next block; after 7 days staking.pending(whale) == 44,349.9 FWA. Run: forge test --match-path test/scratch/P1.t.sol -> FAIL 'an unblessed seller must not recapture its own sinners' tax: 725 < 4000'.","severity":"medium","snippet":"            accPerShare += FullMath.mulDiv(amount, PRECISION, totalStaked);","title":"Sinners' tax is paid straight back to whoever is staked at that instant, so an unblessed seller (or a JIT staker) who stakes one block ahead recovers up to 90% of the 50% tax"},{"citation":"resolved","description":"For every swap whose sender is not the PRAY router, beforeSwap attributes the sell to tx.origin, reads that wallet's blessing to pick the 1% rate, and afterSwap consumes it (src/Hook.sol:206 `if (!buy && _load(BLESSED) != 0) registry.consume(seller, uint256(-int256(prayDelta)));`). The PRAY sold belongs to whoever settles the swap, which need not be tx.origin. A dApp, aggregator route, airdrop claim or any contract a blessed wallet interacts with during its 15-minute window can unlock the PoolManager inside that call, sell its own PRAY with empty hookData at the blessed 1% rate instead of the 50% sinners' tax, and the visitor's blessing (0.5 IMD, a panel approval, one per 24 hours) is spent; the visitor then pays 50% on the sale they filed the plea for, and PRAY stakers lose the tax the stranger's sell owed. tx.origin authorization is a listed prohibited pattern in the uniswap-v4-security reference; README line 99 calls it attribution rather than permission, but the blessing is what authorizes the discount. Reported by three specialists (write_foundry_tests, audit_permissions, audit_flow); merged here. Minimal fix that keeps foreign routers working: treat every non-router sell as unblessed (50%, or 1% while the swarm sleeps) and never consume a blessing for it; blessed sellers, including smart wallets, use PrayRouter, which already attributes the sell to the wallet that pays.","line":161,"path":"src/Hook.sol","reproduction":"State: pool seeded, warp past the decay, swarm awake, Alice holds an approved blessing of 100 PRAY (registry.available(alice) == 100e18). Deploy contract Leech holding 100 PRAY whose doSomethingForUser() calls manager.unlock and in unlockCallback swaps its 100 PRAY for FWA on the PRAY pool with hookData '', settles its own PRAY and takes the FWA to itself. Alice (EOA, tx.origin) calls leech.doSomethingForUser(100e18). Expected: an unblessed party's sell pays SINNER_FEE_BPS (hook.stakingFees() > 0) and registry.available(alice) stays 100e18. Actual: hook fee 0.9875 FWA on 98.75 FWA gross = 99 bps (LOW_FEE_BPS), hook.stakingFees() == 0, registry.available(alice) == 0. Run: forge test --match-path test/scratch/OriginBlessingProof.t.sol -> FAIL \"a stranger's sell consumed alice's blessing: 0 != 100000000000000000000\" (the proof plants the blessing with vm.store at PleaRegistry storage slot 9, the blessings mapping, exactly as an approved plea writes it).","severity":"medium","snippet":"        address seller = sender == address(router) ? abi.decode(data, (address)) : tx.origin;","title":"Blessing attribution by tx.origin: any contract a blessed wallet calls can sell its own PRAY at 1% and consume the wallet's blessing"},{"citation":"resolved","description":"For exact-output sells (amountSpecified > 0, FWA specified) the PRAY input is unknown in beforeSwap, so the hook sets blessed = true whenever available > 0 and charges 1% upfront; afterSwap then calls registry.consume with the actual PRAY paid, which reverts BlessingUnavailable when it exceeds what is left, and the whole swap fails. An exact-input sell in the same situation pays 50% and leaves the blessing intact. The asymmetry means any integration that quotes exact output for a wallet with a small leftover blessing gets a revert wrapped by the PoolManager (HookCallFailed) rather than a trade, and a wallet with 1 wei of blessing left cannot exact-output sell at all until it expires. README line 97 documents this whole-swap rule and the site uses exact input; reported because the exact-input path shows the fallback is possible: in beforeSwap, treat an exact-output sell as blessed only when the maximum PRAY the swap can consume is known to fit, or in afterSwap fall back to the sinner rate instead of reverting (re-collecting the difference on the unspecified side is not possible for a specified-side fee, so the simplest change is to refuse the provisional rate and charge 50% upfront when the quoted PRAY input, e.g. via the router's maxInput passed in hookData, exceeds the blessing).","line":167,"path":"src/Hook.sol","reproduction":"State: mature pool, Alice blessed for 10 PRAY (registry.available(alice) == 10e18). Alice calls PrayRouter.swap(Trade(buy=false, amountSpecified=+20e18 (exact FWA output), maxInput=100e18, minOutput=1, limit, deadline)). Expected by analogy with exact input: the sell executes at 50% and the 10 PRAY blessing stays. Actual: beforeSwap sets blessed (available > 0), afterSwap calls registry.consume(alice, ~40e18 PRAY) which reverts BlessingUnavailable; the swap reverts (test/scratch/Judge.t.sol testExactOutputSellWithSmallBlessingReverts: vm.expectRevert passes and registry.available(alice) is still 10e18 afterwards). The same exact-input sell of 40 PRAY succeeds at 50%.","severity":"low","snippet":"            blessed = available > 0 && (p.amountSpecified > 0 || uint256(-p.amountSpecified) <= available);","title":"An exact-output sell by a wallet with any remaining blessing is judged blessed provisionally and reverts when the PRAY needed exceeds the blessing, instead of falling back to the 50% rate like exact i"},{"citation":"resolved","description":"filePlea compares the requested amount to the caller's PRAY balance only at filing time; consume() later checks only the blessing. A wallet holding 1,000 PRAY borrows 9,000 PRAY from a second wallet (or a flash source), files for 1,000, returns the 9,000, and once approved sells its entire holding at 1%. The panel still sees the amount in the signed question, so the oracle can refuse, but the on-chain rule the README advertises ('limits amount to 10% of the caller's current PRAY balance') gives no guarantee. Either describe it as a filing-time check or enforce the balance when the blessing is granted or consumed (e.g. in consume require token.balanceOf(seller) + staked >= 9 * amount before the sell). Reported by audit_flow; kept at info because the brief does not require the cap.","line":95,"path":"src/PleaRegistry.sol","reproduction":"Wallet A holds 1,000e18 PRAY, Bob holds more. Tx1 (A): registry.filePlea(1_000e18, text) reverts InvalidPlea. Tx2: bob.transfer(A, 9_000e18). Tx3 (A): registry.filePlea(1_000e18, text) succeeds (id 1). Tx4 (A): A.transfer(bob, 9_000e18); A's balance is back to 1,000e18 while the pending plea is for 1,000e18 (test/scratch/Judge.t.sol testFilePleaCapBypassByMovingTokensIn passes). After an approval, registry.available(A) == 1_000e18 and A sells its whole balance at 1%.","severity":"info","snippet":"            amount == 0 || amount > token.balanceOf(msg.sender) / 10 || openPlea[msg.sender] != 0","title":"The 10%-of-balance plea cap is a filing-time snapshot and is bypassed by moving PRAY into the wallet before filing"}],"hash":"d13fc23ec31fc8748598db83e5dac5e754a8d09072d939fc9010bf984ecaf978","nodeId":"4ac771a9-2adb-4c4d-bd91-79976b601928","outcome":"completed","summary":"The review is complete. The working tree shows no tracked changes (git status is clean; `.imd-findings.json` and `test/scratch/` are ignored), and the findings file holds five substantiated findings plus a 27-row coverage record answering all 24 entry points.\n\n**What I found**\n\n- **High, launch.json line 28.** The manifest opens the pool at the one-to-one placeholder price while the brief fixes 0.002 FWA per PRAY and says the base stopped only for this value. The hook accepts any price and initializes once, so the pool would open 500x overpriced with no way to correct it. The notes claim the deployer derives the price, but notes carry no deployment authority.\n- **Medium, StakingVault line 75.** Sinners' tax is credited immediately to whoever is staked, so a seller who stakes one block ahead recovers up to 90% of their own 50% tax, and JIT stakers can take it from resident stakers. All four specialists reported this; I merged them into one finding, confirmed the three attached proofs fail on this code, and carried one as the proof. It is a design decision for the requester and I said so.\n- **Medium, Hook line 161.** Non-router swaps attribute the sell to tx.origin, so any contract a blessed wallet calls during its 15-minute window sells its own PRAY at 1% and burns the wallet's blessing. Three specialists reported it; I wrote a self-contained proof that plants a blessing via storage and fails with the blessing drained and a 99 bps fee.\n- **Low, Hook line 167.** Exact-output sells with any leftover blessing revert instead of falling back to 50%, unlike exact input.\n- **Info, PleaRegistry line 95.** The 10% balance cap on pleas is a filing-time snapshot and is bypassed by moving PRAY in before filing.\n\n**What I dropped**\n\nThe whole-position one-block lock on top-ups and the few-wei buyback budget tolerance are documented, tested behaviour, not defects. The \"no launch.json\" specialist note is stale since the tree now has one.\n\n**What holds**\n\nDelta accounting nets to zero on every path, fee splits and decay are exact, the router pulls only from its caller, all callbacks are manager-bound with committed payloads, FWA only moves with the PoolManager on one side, and the hook deploys within the creation and runtime size limits at about 7.4M gas.","treeHash":null,"usage":{"cachedInputTokens":2157222,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":59306,"runtime":"claude","turns":42,"wallClockMs":1563450}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"03845cacb54c3a73","findings":[{"citation":"resolved","description":"For every swap not initiated by PrayRouter, beforeSwap attributes the sell to tx.origin and afterSwap consumes that wallet's blessing (Hook.sol:206 registry.consume(seller, ...)). The seller is whoever supplied the PRAY, which is the contract calling PoolManager.swap, not tx.origin. So any contract a blessed wallet transacts with during its 15-minute window (a dapp, an aggregator route, an airdrop claim, a malicious 'mint') can sell its OWN PRAY inside that transaction, pay the 1% rate instead of the 50% sinners' tax, and spend the wallet's blessing. The wallet then pays 50% on the sale it filed the plea for, and PRAY stakers lose the 50% tax the stranger's sell should have paid. The README records tx.origin attribution as inherited from the base; it does not record that a third party's PRAY is discounted and the caller's blessing burned. Minimal fix that keeps the design: for non-router senders, only treat the swap as blessed when sender == tx.origin (an EOA selling directly cannot happen in v4 anyway, so in practice: treat every non-router swap as unblessed, or require sender == seller).","line":161,"path":"src/Hook.sol","reproduction":"State: swarm awake, Alice blessed for 100 PRAY (registry.available(alice)==100e18). Leech is an unrelated contract holding 100 PRAY. Alice sends a tx calling Leech.poke(); inside it Leech unlocks the PoolManager and swaps 10 PRAY -> FWA on the PRAY pool with empty hookData. Expected: Leech is unblessed, pays SINNER_FEE_BPS (hook.stakingFees() > 0) and registry.available(alice) stays 100e18. Actual: beforeSwap sets seller = tx.origin = alice, rate = 100 bps, afterSwap calls registry.consume(alice, 10e18); registry.available(alice) == 90e18, hook.stakingFees() == 0 (observed in test/scratch/BlessingTxOrigin.t.sol: '90000000000000000000 != 100000000000000000000').","severity":"low","snippet":"        address seller = sender == address(router) ? abi.decode(data, (address)) : tx.origin;","title":"Any contract a blessed wallet calls can sell its own PRAY at 1% and burn the wallet's blessing (tx.origin attribution)"},{"citation":"resolved","description":"Tax arriving while stakers exist is credited pro rata at once. A seller necessarily holds PRAY, so before an unblessed sell they stake the PRAY they are not selling (one block earlier, which is all SameBlock requires) and receive share = stake/(stake+others) of the 90% staker cut, claimable in the same block as the sell (claim has no block guard). Early in the launch, when few others stake, the effective sinners' tax is ~5% (the 10% treasury cut) rather than 50%, and the 'sinners pay stakers' guarantee reduces to 'sinners pay the treasury'. The README documents this as a brief-mandated property; it is reported so the author can decide, since it hollows out the headline 50% rate for anyone who reads the contract. A streaming or delayed credit (e.g. route every notifyReward through the 7-day stream, or settle rewards only for stake older than N blocks) would close it without an owner.","line":75,"path":"src/StakingVault.sol","reproduction":"State: swarm awake, 30 min after launch, no other stakers. Bob holds 1,100,000 PRAY. Tx1 (block N): bob approves and calls staking.stake(1_000_000e18). Tx2 (block N, same or later): bob sells 100 PRAY exact input via PrayRouter unblessed: hook charges 50% of gross FWA, stakingFees += 0.45*gross. Tx3 (block N): bob calls staking.claim(). Expected per the brief: bob paid 50% tax to other stakers. Actual: staking.pending(bob) == hook.stakingFees() (observed 44437412236110833682 wei both) and claim() returns it, so bob keeps 45% of the gross plus the 50% net proceeds; the only tax he actually paid is the 10% treasury share (5% of gross).","severity":"low","snippet":"            accPerShare += FullMath.mulDiv(amount, PRECISION, totalStaked);","title":"An unblessed seller recaptures up to 90% of their own sinners' tax by staking one block earlier"},{"citation":"resolved","description":"The cap reads the caller's balance at filing time only. A holder of 1,000 PRAY in wallet A who wants a 1,000 PRAY blessing transfers 9,000 PRAY from wallet B to A, files for 1,000, and transfers back; the blessing (if approved) covers the full 1,000 even though A's balance is back to 1,000. The oracle question states the amount, so the panel can still judge it, but the on-chain rule gives no guarantee and the README presents it as a limit ('limits amount to 10% of the caller's current PRAY balance'). Either drop the claim from the README or snapshot and enforce the balance at consume time (e.g. require staked+held balance >= 10*amount in consume).","line":95,"path":"src/PleaRegistry.sol","reproduction":"Wallet A holds 1,000e18 PRAY, wallet B holds 9,000e18. Tx1: B.transfer(A, 9,000e18). Tx2 (A): registry.filePlea(1_000e18, text) succeeds (1,000e18 <= 10,000e18/10). Tx3: A.transfer(B, 9,000e18). After approval registry.available(A) == 1_000e18 and A sells 1,000e18 (its entire holding) at 1%. Expected if the 10% rule meant anything: filePlea reverts InvalidPlea for amount > 100e18.","severity":"info","snippet":"            amount == 0 || amount > token.balanceOf(msg.sender) / 10 || openPlea[msg.sender] != 0","title":"The 10%-of-balance plea cap is only a per-wallet snapshot and is bypassed by moving PRAY in before filing"}],"hash":"d90ac143231d639c8819c36792239da1d599e8ebe8298b3aae237d9746cfcf20","nodeId":"81d9c60e-8926-4513-8ff7-ab96a489f56c","outcome":"completed","summary":"The review is complete. `.imd-findings.json` is written at the repository root with three findings and a 26-row coverage record. No source files were changed; the only other file I created is the proof test under `test/scratch/`, which the task reserves for me.\n\n**What I covered.** I traced every listed entry point through the Execution Trace, Periphery and First Principles checklists: the hook's four swap branches (exact input and output, buy and sell), including the upfront-fee delta, the PartialFill equality, the unspecified-side fee and the hook's net delta per swap. I also traced router settlement, buyback deltas and the floor, the staking accumulator and stream state machine, the registry's attestation path against the live fixture's canonical shape, and the helper libraries. The existing suite passes at baseline.\n\n**Findings.**\n\n- **Low, Hook.beforeSwap line 161.** For any swap not sent through PrayRouter, the seller is tx.origin. A contract a blessed wallet calls during its 15-minute window can sell its own PRAY at 1% and consume the wallet's blessing. Proof test `test/scratch/BlessingTxOrigin.t.sol` fails on the current code with the blessing reduced from 100 to 90 PRAY and zero sinners' tax paid.\n- **Low, StakingVault.notifyReward line 75.** Immediate pro-rata credit lets an unblessed seller stake their remaining PRAY one block earlier and claim back their own tax in the same block as the sell. With no other stakers the effective tax drops from 50% to about 5%. The README documents this as brief-mandated, so it is reported for the author's decision.\n- **Info, PleaRegistry.filePlea line 95.** The 10%-of-balance cap is a filing-time snapshot and is bypassed by moving PRAY in from another wallet before filing.\n\n**What holds.** Fee splits, launch decay, delta accounting on both token orderings, buyback bounds, treasury redemption, staking solvency and no-retroactive-rewards, router allowance isolation, callback authentication, and the oracle binding all behaved as intended under the traces and probes I ran.\n\n**Not reached.** The site front end and the mainnet fork rehearsal are outside my area and need network. The real Intake's handling of the 0.5 IMD payment and the launch manifest itself could not be verified offline.","treeHash":null,"usage":{"cachedInputTokens":2543935,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":64639,"runtime":"claude","turns":51,"wallClockMs":1115885}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"79373c79d1351eba","findings":[{"citation":"resolved","description":"Trust gap (economics x asymmetry). Hook._collect (src/Hook.sol:244, `staking.notifyReward(vaultPart);`) credits 90% of every unblessed sell's 50% tax to StakingVault, and notifyReward distributes it to the CURRENT stakers pro rata at that instant. The only anti-abuse guard is SameBlock (src/StakingVault.sol:94), which stops a stake from being withdrawn in the block it was added. It does not stop the seller from staking its unsold PRAY in block N, selling unblessed in block N+1, claiming, and unstaking in block N+2. The seller then receives back its own share of the tax: with a dominant stake the 50% 'sinners' tax' becomes ~5% (the treasury's 10% of 50%) plus the LP fee, with no plea, no 0.5 IMD, no panel and no 24h cooldown. The same mechanism lets any MEV bot JIT-stake in front of other people's unblessed sells and take most of the tax away from long-term stakers, with one block of PRAY price exposure. The brief's guarantees 'unblessed pay 50%' and 'that sinners' tax goes to PRAY stakers' are therefore only true for sellers who hold a small share of the stake. README.md line 77 documents the property as a known trade-off of immediate accPerShare distribution; it is reported here because the brief's requirement for accPerShare and 'no rewards for tax that arrived before staking' can be met while still closing the loop (e.g. stream every tax receipt over time the way held tax already is, so a seller must stay staked and exposed through the stream, or exclude the seller's own stake from the distribution of its own sell). Changing this is a design decision for the requester.","line":75,"path":"src/StakingVault.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 {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {Hook} from \"src/Hook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {Token} from \"src/Token.sol\";\nimport {PrayRouter} from \"src/PrayRouter.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\n\n/// @dev Stand-in for FWA: any address may mint, transfers unrestricted (the hook never transfers FWA\n/// wallet-to-wallet, so the restriction is irrelevant to this scenario).\ncontract ScratchFWA is ERC20 {\n    constructor() ERC20(\"Fake World Assets\", \"FWA\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice An unblessed seller who stakes PRAY one block ahead recaptures almost the whole\n/// sinners' tax: the brief's 50% sell fee collapses to ~5% (the treasury's 10% share) for any\n/// holder with a dominant share of the stake, with no plea and no panel.\ncontract SinnerSelfStakeTest is Test, IUnlockCallback {\n    address internal constant FWA = 0xa0Df17B5aC76ABaBA36E1450E2cbCd18A620C845;\n\n    IPoolManager internal manager;\n    Token internal token;\n    Hook internal hook;\n    PrayRouter internal router;\n    StakingVault internal staking;\n    PoolKey internal key;\n    address internal bob;\n    address internal carol;\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1_800_000_000);\n        vm.roll(30_000_000);\n        bob = makeAddr(\"bob\");\n        carol = makeAddr(\"carol\");\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(FWA, address(new ScratchFWA()).code);\n        token = new Token();\n\n        bytes memory creation =\n            abi.encodePacked(type(Hook).creationCode, abi.encode(manager, address(token), address(this)));\n        bytes32 hash = keccak256(creation);\n        for (uint256 salt = 0; salt < 200_000; ++salt) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.PRAY)) continue;\n            address deployed;\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(creation, 32), mload(creation), salt)\n            }\n            require(deployed == predicted, \"deploy failed\");\n            hook = Hook(deployed);\n            break;\n        }\n        require(address(hook) != address(0), \"mine failed\");\n        router = hook.router();\n        staking = hook.staking();\n\n        bool pray0 = address(token) < FWA;\n        key = PoolKey(\n            Currency.wrap(pray0 ? address(token) : FWA),\n            Currency.wrap(pray0 ? FWA : address(token)),\n            12500,\n            60,\n            IHooks(address(hook))\n        );\n        manager.initialize(key, 1 << 96);\n\n        ScratchFWA(FWA).mint(address(this), 100_000_000 ether);\n        manager.unlock(abi.encode(ModifyLiquidityParams(-887220, 887220, 50_000_000 ether, bytes32(0))));\n\n        token.transfer(bob, 2_000_000 ether);\n        token.transfer(carol, 100_000 ether);\n        vm.startPrank(bob);\n        token.approve(address(router), type(uint256).max);\n        token.approve(address(staking), type(uint256).max);\n        vm.stopPrank();\n        vm.prank(carol);\n        token.approve(address(staking), type(uint256).max);\n\n        // Past the launch decay; the swarm is awake, so an unblessed sell is a sinner's sell.\n        vm.warp(hook.launchedAt() + 1800);\n        assertFalse(hook.registry().sleeping());\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (BalanceDelta d,) = manager.modifyLiquidity(key, abi.decode(data, (ModifyLiquidityParams)), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 d) private {\n        if (d < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(-int256(d)));\n            manager.settle();\n        }\n    }\n\n    function _sell(address who, uint256 amount) private returns (uint256 received) {\n        bool zeroForOne = Currency.unwrap(key.currency0) == address(token);\n        uint160 lim = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(who, who);\n        (, received) = router.swap(PrayRouter.Trade(false, -int256(amount), amount, 1, lim, block.timestamp));\n    }\n\n    function testSinnerRecapturesOwnTaxByStakingOneBlockAhead() public {\n        // Carol is an honest staker with 100,000 PRAY.\n        vm.prank(carol);\n        staking.stake(100_000 ether);\n\n        // Bob holds 2,000,000 PRAY, has no blessing, and wants to dump 100,000 PRAY.\n        // Block N: he stakes the 1,900,000 PRAY he is not selling.\n        vm.prank(bob);\n        staking.stake(1_900_000 ether);\n        vm.roll(block.number + 1);\n        vm.warp(block.timestamp + 12);\n\n        // Block N+1: unblessed exact-input sell of 100,000 PRAY.\n        uint256 received = _sell(bob, 100_000 ether);\n        uint256 fee = hook.totalFees();\n        uint256 gross = received + fee;\n        assertApproxEqAbs(fee, gross / 2, 1, \"hook charged 50% of gross\");\n        assertEq(hook.stakingFees(), fee - fee / 10, \"90% of the tax went to the staking vault\");\n\n        // Bob immediately holds 95% of the stakers' share as claimable FWA.\n        uint256 bobPending = staking.pending(bob);\n        assertApproxEqAbs(bobPending, (fee - fee / 10) * 19 / 20, 2, \"bob's pro-rata share\");\n\n        vm.prank(bob);\n        uint256 claimed = staking.claim();\n        assertEq(claimed, bobPending);\n\n        // Block N+2: Bob leaves. No lock applies.\n        vm.roll(block.number + 1);\n        vm.prank(bob);\n        staking.unstake(1_900_000 ether);\n\n        // Bob's effective hook fee on an *unblessed* sell: what he paid minus what the tax\n        // handed straight back to him.\n        uint256 effective = fee - claimed;\n        uint256 effectiveBps = effective * 10_000 / gross;\n        emit log_named_uint(\"gross FWA proceeds\", gross);\n        emit log_named_uint(\"hook fee charged\", fee);\n        emit log_named_uint(\"tax returned to seller\", claimed);\n        emit log_named_uint(\"effective sell fee bps\", effectiveBps);\n\n        // The brief: unblessed sells pay 50%. A sinner who stakes pays ~7% here, and ~5% as the\n        // sole staker. This assertion fails on the current code and passes once the seller cannot\n        // collect its own tax (e.g. the tax is streamed over time, or the seller's own stake is\n        // excluded from the distribution of its own sell).\n        assertGe(effectiveBps, 4_000, \"an unblessed seller must not recapture its own sinners' tax\");\n    }\n}","reproduction":"State: pool seeded, 30 minutes after launch, swarm awake (registry.sleeping() == false). Carol stakes 100,000 PRAY. Bob holds 2,000,000 PRAY and has no blessing. Block N: bob.stake(1,900,000 PRAY). Block N+1: bob sells 100,000 PRAY exact-input through PrayRouter (unblessed). Expected per brief: Bob pays 50% of gross FWA proceeds. Actual (test/scratch/SinnerSelfStake.t.sol, numbers from the run): gross 98,555.35 FWA; hook fee 49,277.68 FWA (50%); staking.pending(bob) == 42,132.41 FWA immediately (95% of the 90% stakers' share); bob.claim() pays it; block N+2 bob.unstake(1,900,000) succeeds (no lock). Net fee paid by Bob = 49,277.68 - 42,132.41 = 7,145.27 FWA = 7.25% of gross (5% when Bob is the sole staker). Honest staker Carol receives only 5% of the tax. Run: forge test --match-path test/scratch/SinnerSelfStake.t.sol -vv -> FAIL 'an unblessed seller must not recapture its own sinners' tax: 725 < 4000'.","severity":"medium","snippet":"accPerShare += FullMath.mulDiv(amount, PRECISION, totalStaked);","title":"Unblessed seller recaptures its own sinners' tax by staking one block ahead: the 50% sell fee collapses to ~5-7%"},{"citation":"resolved","description":"Access control / trust gap (access x asymmetry). For every swap whose sender is not the PRAY router, the hook attributes the sell to tx.origin, and afterSwap then calls registry.consume(tx.origin, prayDelta). Attribution and payment are thus decoupled: the wallet whose blessing is checked and consumed need not be the wallet whose PRAY is sold. Any contract Alice interacts with during her 15-minute window (an aggregator, an NFT mint, a 'claim' button, an airdrop contract) can, inside that call, unlock the PoolManager and sell its OWN PRAY up to Alice's blessed amount at the 1% rate instead of 50%; Alice's blessing (which cost 0.5 IMD, a panel verdict and a 24-hour cooldown) is consumed and she cannot file again for 24 hours. Stakers also lose the 50% tax that sell would otherwise have paid. The brief says beforeSwap 'reads hookData and checks the blessing on sells'; it does not ask for tx.origin, and README.md line 99 documents the tx.origin fallback as a known limitation. The payer of a non-router swap is unknowable to the hook, so a minimal fix is to treat every non-router sell as unblessed (rate 50%, no consume) and let blessed sellers use the PRAY router, which already attributes the sell to the wallet that actually pays. Severity low because it needs the victim to call attacker-chosen code inside her window.","line":161,"path":"src/Hook.sol","reproduction":"State: mature pool, Alice blessed for 100 PRAY (registry.available(alice) == 100e18). Deploy contract Leech holding 100 PRAY whose function doSomethingForAlice() calls manager.unlock and, in unlockCallback, manager.swap(key, exact-input sell of its 100 PRAY, hookData '') then settles its own PRAY and takes the FWA to itself. Alice (EOA, tx.origin) calls leech.doSomethingForAlice(). Expected: a sell by an unblessed party pays 50% and leaves Alice's blessing intact. Actual: hook.totalFees() ~= leechFWA / 99 (1% blessed rate), registry.available(alice) == 0, hook.stakingFees() == 0. Reproduced by test/scratch/OriginBlessing.t.sol::testThirdPartyContractSpendsAlicesBlessingOnItsOwnPray (passes, i.e. the attack succeeds). Existing test testForeignRouterCannotSpoofHookData only shows hookData is ignored for foreign routers; it does not cover a payer different from tx.origin.","severity":"low","snippet":"address seller = sender == address(router) ? abi.decode(data, (address)) : tx.origin;","title":"Any contract a blessed wallet calls can spend that wallet's blessing on its own PRAY (tx.origin attribution)"}],"hash":"f00675d79c55ca0de1058901a523fe736541d44534c1f5ec52dc5970b6a687d3","nodeId":"2f026325-bfa8-43e1-ad6a-be6c4b0f7dbf","outcome":"completed","summary":"The review is complete. The findings file is on disk, the harness ignores it and the scratch directory by design, and no tracked file was modified.\n\n**Outcome**\n\nI audited the PRAY contracts for access control, trust gaps and asymmetry. The permission model is tight: no owner, every hook callback and vault callback is restricted to the PoolManager, every unlock callback is bound to a payload hash committed by the in-flight call, funds leave only to fixed or self-chosen recipients, and all rates are constants. All 98 existing tests pass. I found two defects, both reproduced with runnable tests.\n\n**Findings written to `.imd-findings.json`**\n\n- **Medium, with proof.** An unblessed seller recaptures its own sinners' tax by staking unsold PRAY one block before selling. The staking vault credits tax to current stakers instantly, and the only guard blocks same-block exit. In my test a seller holding 95% of the stake paid an effective 7.25% instead of 50%, and 5% as sole staker. The same path lets MEV bots JIT-stake in front of others' sells. The README documents this as a trade-off, but it voids the brief's 50% guarantee for any large holder, so I reported it and left the design call to the requester. Proof at `test/scratch/SinnerSelfStake.t.sol` fails on the current code.\n- **Low.** For non-router swaps the hook attributes the sell to tx.origin and consumes that wallet's blessing. A contract Alice calls during her 15-minute window can sell its own PRAY at 1% on her blessing, leaving her with nothing and a 24-hour cooldown. Reproduced in `test/scratch/OriginBlessing.t.sol`.\n\n**Coverage**\n\nAll 24 listed entry points have a row, plus three invariant rows. Three rows point at findings, the rest hold with a one-line reason. I dropped a manifest lead after confirming no launch.json exists yet, which matches the brief. Areas I did not reach in depth: sustained TWAP manipulation of the buyback floor and the site code, both outside my assigned guides and documented by the author as known limits.","treeHash":null,"usage":{"cachedInputTokens":1762665,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":48266,"runtime":"claude","turns":45,"wallClockMs":811573}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"672c29f92850efe0","findings":[{"citation":"resolved","description":"The hook routes 90% of every unblessed sell fee to StakingVault.notifyReward in the same swap, and notifyReward credits it pro rata to whoever is staked at that instant (line 75), with no exclusion of the seller and no time weighting. The only guard is SameBlock on unstake (line 94), which does not stop the seller from staking the rest of their PRAY one block (or one transaction) earlier, selling unblessed, and immediately claiming most of their own tax back; claim() has no same-block restriction. Three concrete shapes, all measured on this code: (a) self-stake: a holder stakes 9.9M PRAY in block N and sells 100k PRAY unblessed in block N+1 while an honest staker holds 10k; the hook charges 49,277.68 FWA of tax and routes 44,349.91 FWA to stakers, of which the seller's own pending() is 44,305.16 FWA and the honest staker's is 44.75 FWA, so the effective sell fee is 504 bps instead of 5000 bps; (b) backlog: with nobody staked the same sell parks 44,349.91 FWA in held; the seller stakes 1 wei of PRAY the next block and after 7 days pending() is the full 44,349.91 FWA (100% of what was routed to stakers); (c) third-party JIT: a searcher who sees an unblessed sell in the mempool stakes in the same block before it, claims in the same block, and unstakes the next block, taking the tax from the resident stakers. The brief's economics (unblessed pay 50%, the tax goes to PRAY stakers, plea or pay) are defeated for any seller or searcher with PRAY on hand, which is exactly the large holders the tax is meant to bind, and resident stakers lose the reward the design promises them. The README and docs/REVIEW.md record the whale case as a known property; the measured size (90% of the tax back for the sinner; 100% via the held backlog) and the same-block JIT variant are stated here so the requester can decide. Minimal fixes that keep the agreed design: exclude the seller's own stake from the distribution of the tax their swap generated (notifyReward(amount, seller) settles the seller first and distributes over totalStaked - staked[seller], or checkpoints their debt past it), and/or stream all tax over a period so that stakes must be held through it, and require the first stake after a held backlog to be older than the backlog's tax.","line":75,"path":"src/StakingVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\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 {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {Hook} from \"src/Hook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {Token} from \"src/Token.sol\";\nimport {PrayRouter} from \"src/PrayRouter.sol\";\nimport {StakingVault} from \"src/StakingVault.sol\";\n\n/// @dev Stand-in for FWA: ordinary ERC-20 that only allows transfers with the PoolManager on one side.\ncontract ScratchFWA is ERC20 {\n    address public manager;\n    constructor() ERC20(\"FWA\", \"FWA\") {}\n    function setManager(address m) external { manager = m; }\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n    function _update(address from, address to, uint256 amount) internal override {\n        if (from != address(0) && to != address(0) && from != manager && to != manager) revert(\"InvalidTransfer\");\n        super._update(from, to, amount);\n    }\n}\n\ncontract ScratchSeeder is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n    function seed(PoolKey memory k, int256 liquidity, int24 lower, int24 upper) external {\n        manager.unlock(abi.encode(k, ModifyLiquidityParams(lower, upper, liquidity, bytes32(0)), msg.sender));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (PoolKey memory k, ModifyLiquidityParams memory p, address payer) =\n            abi.decode(data, (PoolKey, ModifyLiquidityParams, address));\n        (BalanceDelta d,) = manager.modifyLiquidity(k, p, \"\");\n        _settle(k.currency0, d.amount0(), payer);\n        _settle(k.currency1, d.amount1(), payer);\n        return \"\";\n    }\n    function _settle(Currency c, int128 d, address payer) private {\n        if (d < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), uint256(-int256(d)));\n            manager.settle();\n        } else if (d > 0) {\n            manager.take(c, payer, uint256(int256(d)));\n        }\n    }\n}\n\ncontract SinnerRecaptureProof is Test {\n    address constant FWA = 0xa0Df17B5aC76ABaBA36E1450E2cbCd18A620C845;\n    IPoolManager manager;\n    Token token;\n    Hook hook;\n    PrayRouter router;\n    StakingVault staking;\n    PoolKey key;\n    address whale = makeAddr(\"whale\");\n    address small = makeAddr(\"small\");\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1_800_000_000);\n        vm.roll(30_000_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(FWA, address(new ScratchFWA()).code);\n        ScratchFWA(FWA).setManager(address(manager));\n        token = new Token();\n        bytes memory creation =\n            abi.encodePacked(type(Hook).creationCode, abi.encode(manager, address(token), address(this)));\n        bytes32 hash = keccak256(creation);\n        for (uint256 salt = 0; salt < 300_000; ++salt) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.PRAY)) continue;\n            address deployed;\n            assembly (\"memory-safe\") { deployed := create2(0, add(creation, 32), mload(creation), salt) }\n            require(deployed == predicted, \"deploy\");\n            hook = Hook(deployed);\n            break;\n        }\n        require(address(hook) != address(0), \"mine\");\n        router = hook.router();\n        staking = hook.staking();\n        bool pray0 = address(token) < FWA;\n        key = PoolKey(\n            Currency.wrap(pray0 ? address(token) : FWA),\n            Currency.wrap(pray0 ? FWA : address(token)),\n            12500,\n            60,\n            IHooks(address(hook))\n        );\n        manager.initialize(key, 1 << 96);\n        ScratchSeeder seeder = new ScratchSeeder(manager);\n        ScratchFWA(FWA).mint(address(this), 100_000_000 ether);\n        token.approve(address(seeder), type(uint256).max);\n        IERC20(FWA).approve(address(seeder), type(uint256).max);\n        seeder.seed(key, 50_000_000 ether, -887220, 887220);\n        token.transfer(whale, 10_000_000 ether);\n        token.transfer(small, 10_000 ether);\n        vm.startPrank(whale);\n        token.approve(address(router), type(uint256).max);\n        token.approve(address(staking), type(uint256).max);\n        vm.stopPrank();\n        vm.startPrank(small);\n        token.approve(address(router), type(uint256).max);\n        token.approve(address(staking), type(uint256).max);\n        vm.stopPrank();\n        vm.warp(hook.launchedAt() + 1800);\n    }\n\n    function sell(address who, uint256 amount) internal returns (uint256 received) {\n        bool pray0 = address(token) < FWA;\n        uint160 lim = pray0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(who, who);\n        (, received) = router.swap(PrayRouter.Trade(false, -int256(amount), amount, 1, lim, block.timestamp));\n    }\n\n    /// A seller who stakes the rest of their holdings one block earlier takes the sinners' tax back.\n    function testSinnerTakesOwnTaxBackThroughStaking() public {\n        // A small honest staker is present.\n        vm.prank(small);\n        staking.stake(10_000 ether);\n        // The whale stakes 9.9M PRAY in block N and sells 100k PRAY unblessed in block N+1.\n        vm.prank(whale);\n        staking.stake(9_900_000 ether);\n        vm.roll(block.number + 1);\n        uint256 received = sell(whale, 100_000 ether);\n        uint256 tax = hook.totalFees();\n        uint256 toStakers = hook.stakingFees();\n        assertEq(toStakers, tax - tax / 10);\n        uint256 whalePending = staking.pending(whale);\n        uint256 smallPending = staking.pending(small);\n        emit log_named_uint(\"FWA received by seller\", received);\n        emit log_named_uint(\"sinners tax\", tax);\n        emit log_named_uint(\"tax routed to stakers\", toStakers);\n        emit log_named_uint(\"tax recaptured by the seller\", whalePending);\n        emit log_named_uint(\"tax reaching the honest staker\", smallPending);\n        emit log_named_uint(\"effective tax bps\", (tax - whalePending) * 10000 / (received + tax));\n        // The sinner must not be paid their own sinners' tax: the honest staker should get it all.\n        assertEq(whalePending, 0, \"seller recaptured the sinners' tax through the staking vault\");\n    }\n}","reproduction":"Deploy as in the project tests (PoolManager, FWA mock at 0xa0Df17B5aC76ABaBA36E1450E2cbCd18A620C845, Hook mined for flags 8396, pool initialised, full-range liquidity), warp to launchedAt + 1800 so the swarm is awake and buys are at 1%. Variant (a): small.stake(10_000e18); whale.stake(9_900_000e18) in block N; vm.roll(N+1); whale sells 100_000e18 PRAY unblessed through PrayRouter.swap(Trade(false, -100000e18, 100000e18, 1, limit, deadline)). Expected: whale's pending() is 0 and the honest staker's pending() is ~44,349.9 FWA. Actual: hook.totalFees() = 49,277.676 FWA, hook.stakingFees() = 44,349.909 FWA, staking.pending(whale) = 44,305.156 FWA, staking.pending(small) = 44.753 FWA; the seller's net cost is 5.04% of gross proceeds. Variant (b): no stakers; whale sells 100_000e18 unblessed; staking.held() = 44,349.909 FWA; vm.roll(+1); whale.stake(1); vm.warp(+7 days); staking.pending(whale) = 44,349.909 FWA. Variant (c): in one block, searcher.stake(X) ordered before victim's unblessed sell, searcher.claim() after it, searcher.unstake(X) next block; the searcher's pending equals X/(X+totalStaked) of 90% of the tax. The scratch test test/scratch/SinnerRecaptureProof.t.sol fails on this code with these numbers.","severity":"medium","snippet":"            accPerShare += FullMath.mulDiv(amount, PRECISION, totalStaked);","title":"Sinners' tax is paid back to the sinner (or a same-block JIT staker) through the staking vault, so the 50% unblessed sell fee is effectively ~5% for anyone who stakes first"},{"citation":"resolved","description":"The brief says the accepted base stopped at the manifest for the missing pool price and now supplies it, but the working tree still has no launch.json; README states this explicitly. The handoff file deployment-parameters.json is not schema-shaped: the hook block carries \"flags\": 8396 instead of a permissions list, the pool block carries an initialSqrtPriceX96 object keyed by token order instead of a single decimal initialPrice string, contract names are written as path:Name rather than the contract name, and supply, chainId, children, initialPriceAndLiquidity, site and policy are not manifest keys. None of this is a code defect and the hook itself accepts both orderings, fee 12500, spacing 60 and any valid price, so a correct launch.json can be written from it: hook contract Hook, constructorArgs [\"$poolManager\",\"$token\",\"$factory\"], permissions [beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta], token Token / pray / PRAY / 18, pool pairedCurrency 0xa0df17b5ac76ababa36e1450e2cbcd18a620c845, fee 12500, tickSpacing 60, initialPrice 3543191142285914205922034323 if PRAY sorts below FWA else 1771595571142957102961017161607. Recorded so the manifest step is not skipped on the assumption that this file is the manifest.","line":13,"path":"deployment-parameters.json","reproduction":"ls launch.json in the repository root: no such file. Read deployment-parameters.json lines 12-17 and 24-31: \"flags\": 8396 and \"initialSqrtPriceX96\": {...} where the schema expects \"permissions\": [...] and \"initialPrice\": \"<decimal>\". Expected: a schema-valid launch.json for the launch step. Actual: only the non-schema handoff file exists.","severity":"info","snippet":"    \"contract\": \"src/Hook.sol:Hook\",","title":"No launch.json in the tree; deployment-parameters.json uses keys the manifest schema drops (flags, initialSqrtPriceX96 object, supply, chainId, children, policy) so the manifest step must still be wri"}],"hash":"f93c1415e67571bca28a0aef301b96820bc273ff1618edfb9c4f94e8292edb06","nodeId":"f7d005fa-2fa8-4e1d-b16f-66868cad1799","outcome":"completed","summary":"The review is complete. No tracked file was changed. The findings and coverage record are in `.imd-findings.json`, and the scratch tests under `test/scratch/` reproduce the numbers.\n\n**Outcome.** One substantive economic defect and one informational note. The fee math, exact splits, launch decay, claim-based settlement, staking accounting and buyback floor all held under tracing and under the scratch tests I ran.\n\n**Finding 1, medium: the sinners' tax flows back to the sinner.** The hook routes 90% of each unblessed sell fee to the staking vault in the same swap, and the vault credits it pro rata to whoever is staked at that instant. Nothing excludes the seller. The only guard blocks same-block unstaking, which a seller never needs.\n\n| Scenario measured on this code | Tax routed to stakers | Seller gets back |\n|---|---|---|\n| Seller stakes 9.9M PRAY one block before selling 100k, honest staker holds 10k | 44,349.9 FWA | 44,305.2 FWA (effective fee 5.04%, not 50%) |\n| Nobody staked, seller sells, then stakes 1 wei and waits 7 days | 44,349.9 FWA | 44,349.9 FWA (100% of it) |\n\nA third variant lets a searcher stake in the same block ahead of a victim's sell, claim, and unstake next block. The README records the whale case as a known property. I reported it anyway because the measured recapture defeats the brief's \"plea or pay 50%\" economics for exactly the large holders it targets, and because the held-backlog and same-block variants are not documented. The proof test `test/scratch/SinnerRecaptureProof.t.sol` fails on the current code.\n\n**Finding 2, info.** There is still no `launch.json`. The handoff file uses keys the manifest schema drops, so the manifest step must still write a schema-valid file. The constructor arguments, permissions and prices it needs are listed in the finding.\n\n**Checked and found sound.** The buyback floor accepts ordinary buybacks on a launch-shaped one-sided position after 200k FWA of launch-window buys, so the minimum-budget rule and the 5% floor do not deadlock the burn vault. An atomic sandwich around a 50,000 FWA buyback while the swarm sleeps nets under 3 FWA. The staking vault's solvency, stream conservation and rounding directions hold on every path I traced.\n\n**Coverage.** All 24 listed entry points have a row, plus three invariant rows. Two rows point at finding 1 and the rest hold. The plea registry and oracle entry points are outside my area and were traced only for the fees they move.","treeHash":null,"usage":{"cachedInputTokens":4404522,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":70352,"runtime":"claude","turns":52,"wallClockMs":1357075}}],"verification":[{"checks":[{"durationMs":124022,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 123.72s\nCompiler run successful with warnings:\nWarning (8760): This declaration has the same name as another declaration.\n   --> test/invariants/StakingInvariant.t.sol:114:21:\n    |\n114 |     function _limit(bool buy) internal view returns (uint160) {\n    |                     ^^^^^^^^\nNote: The other declaration is here:\n   --> test/invariants/StakingInvariant.t.sol:311:5:\n    |\n311 |     function buy(uint256 seed, uint256 amount) external {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (8760): This declaration has the same name as another declaration.\n   --> test/invariants/StakingInvariant.t.sol:120:34:\n    |\n120 |     function _trade(address who, bool buy, int256 amount, uint256 max)\n    |                                  ^^^^^^^^\nNote: The other declaration is here:\n   --> test/invariants/StakingInvariant.t.sol:311:5:\n    |\n311 |     function buy(uint256 seed, uint256 amount) external {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/Registry.t.sol:224:5:\n    |\n224 |     function testFuzzJsonEscapeRoundTrip(bytes calldata raw) public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/BurnVault.sol:68:16\n   │\n68 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:31:17\n   │\n31 │                 revert InvalidText();\n   │                 ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:33:36\n   │\n33 │             if (i + n >= s.length) revert InvalidText();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:36:43\n   │\n36 │                 if (d < 0x80 || d > 0xbf) revert InvalidText();\n   │                                           ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:42:15\n   │\n42 │             ) revert InvalidText();\n   │               ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n    ╭▸ src/StakingVault.sol:125:16\n    │\n125 │         delete active;\n    │                ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:57:69\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\n   │                                                                     ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaRegistry.sol:29:38\n   │\n29 │     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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/BurnVault.sol:45:50\n   │\n45 │         if (budget == 0 || budget > MAX_SPEND || block.timestamp < lastBuyback + 60) revert InvalidBuyback();\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/PleaRegistry.sol:83:16\n   │\n83 │         return block.timestamp >= lastAnswer + 24 hours;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:62:27\n   │\n62 │                         ? bytes1(\"b\")\n   │                           ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:36\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:60\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                            ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaRegistry.sol:88:16\n   │\n88 │         return block.timestamp < b.until ? b.remaining : 0;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:63:84\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:98\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                                  ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/BurnVault.sol:61:9\n   │\n61 │         emit BoughtAndBurned(msg.sender, spend, reward, burned);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:107:16\n    │\n107 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n108 │ ┃             '{\"answerType\":\"bool\",\"chainId\":1,\"definitions\":{\"rubric\":\"',\n109 │ ┃             RUBRIC,\n110 │ ┃             '\"},\"evidence\":\"panel\",\"question\":\"',\n111 │ ┃             escape(q),\n112 │ ┃             '\",\"v\":1,\"window\":'\n113 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:57:55\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\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/PleaRegistry.sol:96:43\n   │\n96 │                 || (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours)\n   │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:117:16\n    │\n117 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n118 │ ┃             p,\n119 │ ┃             '{\"hours\":1},\"panelSize\":7,\"quorum\":5,\"validForSeconds\":900,\"consumer\":{\"chainId\":1,\"verifyingContract\":\"',\n120 │ ┃             Strings.toHexString(registry),\n121 │ ┃             '\"}}'\n122 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PleaRegistry.sol:122:9\n    │\n122 │         emit PleaFiled(id, msg.sender, requestId, amount, text);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:127:13\n    │\n127 │ ┏             abi.encodePacked(\n128 │ ┃                 p,\n129 │ ┃                 '{\"fromBlock\":',\n130 │ ┃                 Strings.toString(fromBlock),\n    ‡ ┃\n133 │ ┃                 \"}}\"\n134 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:103:13\n    │\n103 │             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[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:77:18\n   │\n77 │                 -int256(spend),\n   │                  ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:23\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\n   │                       ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:55\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\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/BurnVault.sol:89:26\n   │\n89 │         uint256 burned = uint256(int256(output));\n   │                          ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/PrayRouter.sol:64:16\n   │\n64 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PrayRouter.sol:42:13\n   │\n42 │             block.timestamp > trade.deadline || trade.maxInput == 0\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/PrayRouter.sol:56:9\n   │\n56 │         emit Traded(msg.sender, trade.buy, paid, received);\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/StakingVault.sol:73:13\n   │\n73 │             emit RewardReceived(amount, true);\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/StakingVault.sol:76:13\n   │\n76 │             emit RewardReceived(amount, false);\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:43:37\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 0\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/PrayRouter.sol:43:45\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 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/StakingVault.sol:197:9\n    │\n197 │         emit StreamStarted(streamRemaining, streamEnd);\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/StakingVault.sol:89:9\n   │\n89 │         emit Staked(msg.sender, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:74:24\n   │\n74 │         uint256 paid = uint256(-int256(inputDelta));\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/PrayRouter.sol:75:28\n   │\n75 │         uint256 received = uint256(int256(outputDelta));\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/PrayRouter.sol:78:58\n   │\n78 │                 || (t.amountSpecified > 0 && received != uint256(t.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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:106:9\n    │\n106 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:118:9\n    │\n118 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/StakingVault.sol:117:9\n    │\n117 │         manager.unlock(data);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:147:13\n    │\n147 │         if (streamEnd <= block.timestamp || totalStaked == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:159:57\n    │\n159 │         if (streamRemaining == 0 || totalStaked == 0 || block.timestamp <= lastUpdate) return 0;\n    │                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:160:13\n    │\n160 │         if (block.timestamp >= streamEnd) return streamRemaining;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:166:13\n    │\n166 │         if (released > 0) {\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/PleaRegistry.sol:162:43\n    │\n162 │         if (p.status != Status.Pending || block.timestamp >= uint256(p.filedAt) + PENDING_LIFETIME) {\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/PleaRegistry.sol:180:39\n    │\n180 │             if (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours) {\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/PleaRegistry.sol:187:9\n    │\n187 │ ┏         emit PleaAnswered(\n188 │ ┃             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\n189 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:184:23\n    │\n184 │             b.until = uint64(block.timestamp + 15 minutes);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:185:29\n    │\n185 │             b.lastGranted = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:188:50\n    │\n188 │             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\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/PleaRegistry.sol:194:43\n    │\n194 │         if (p.status != Status.Pending || block.timestamp < uint256(p.filedAt) + PENDING_LIFETIME) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-events-arithmetic]: `cumulative` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:266:9\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observedAt` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:267:9\n    │\n267 │         observedAt = uint64(block.timestamp);\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:223:9\n    │\n223 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:129:9\n    │\n129 │         emit PoolOpened(PoolId.unwrap(poolId), address(registry), address(router), address(vault));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:126:22\n    │\n126 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:128:39\n    │\n128 │         observations[0] = Observation(uint64(block.timestamp), 0);\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/Hook.sol:136:13\n    │\n136 │             elapsed >= DECAY ? LOW_FEE_BPS : LAUNCH_FEE_BPS - (LAUNCH_FEE_BPS - LOW_FEE_BPS) * elapsed / DECAY;\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/Hook.sol:238:9\n    │\n238 │         emit FeeCollected(seller, buy, fee, vaultPart, treasuryPart, rate);\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/Hook.sol:243:13\n    │\n243 │             emit SinnersTax(seller, fee, vaultPart, treasuryPart);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:167:66\n    │\n167 │             blessed = available > 0 && (p.amountSpecified > 0 || uint256(-p.amountSpecified) <= available);\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/Hook.sol:179:54\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:179:84\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:183:23\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:183:31\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:189:63\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/Hook.sol:189:70\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:204:34\n    │\n204 │         address seller = address(uint160(_load(SELLER)));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/Hook.sol:206:67\n    │\n206 │         if (!buy && _load(BLESSED) != 0) registry.consume(seller, uint256(-int256(prayDelta)));\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/Hook.sol:211:51\n    │\n211 │             int256 expected = p.amountSpecified + int256(_load(UPFRONT));\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:214:36\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:214:65\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:218:23\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:218:31\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:225:44\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(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/Hook.sol:225:51\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                   ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:260:9\n    │\n260 │         emit TreasuryPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Hook.sol:268:13\n    │\n268 │         if (block.timestamp >= uint256(observations[observationIndex].time) + 60) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:266:23\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\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/Hook.sol:266:49\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │                                                 ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:267:22\n    │\n267 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:270:58\n    │\n270 │             observations[observationIndex] = Observation(uint64(block.timestamp), cumulative);\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/Hook.sol:275:29\n    │\n275 │         if (!initialized || block.timestamp < launchedAt + 30 minutes) revert HistoryTooShort();\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/Hook.sol:279:38\n    │\n279 │             if (o.time > old.time && uint256(o.time) + 30 minutes <= block.timestamp) old = o;\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:282:66\n    │\n282 │         int256 current = int256(cumulative) + int256(lastTick) * int256(block.timestamp - observedAt);\n    │                                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:37\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:45\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\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/Hook.sol:285:93\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                                                                             ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:286:52\n    │\n286 │         uint160 sqrt = TickMath.getSqrtPriceAtTick(int24(average));\n    │                                                    ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:86:9\n    │\n 86 │         vm.warp(hook.launchedAt() + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    ‡\n250 │         vm.warp(hook.launchedAt() + 1800);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/Hook.t.sol:174:17\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:178:9\n    │\n178 │         vm.warp(registry.lastAnswer() + 86400);\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/HookEdge.t.sol:298:21\n    │\n298 │         uint256 t = block.timestamp;\n    │                     ━━━━━━━━━━━━━━━\n299 │         vm.warp(t + 59);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/HookEdge.t.sol:316:9\n    │\n316 │         vm.warp(vm.getBlockTimestamp() + 10 minutes);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/HookEdge.t.sol:398:13\n    │\n398 │             vm.warp(vm.getBlockTimestamp() + 60);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/Registry.t.sol:94:17\n   │\n94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:178:17\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:238:17\n    │\n238 │         vm.warp(block.timestamp + 86400);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/RegistryEdge.t.sol:215:9\n    │\n215 │         vm.warp(vm.getBlockTimestamp() + 24 hours);\n    │         ────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/RegistryEdge.t.sol:215:9\n    │\n215 │         vm.warp(vm.getBlockTimestamp() + 24 hours);\n    │         ────────────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Staking.t.sol:347:9\n    │\n347 │         vm.warp(vm.getBlockTimestamp() + 1 days);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/StakingEdge.t.sol:203:9\n    │\n203 │         vm.warp(vm.getBlockTimestamp() + 1 days);\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":1268,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testProtocolDigestSignatureAndSelector() (gas: 713241)\n[PASS] testRealMainnetSignerAttestation() (gas: 3350374)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.98ms (1.49ms CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP] testForkBlessedSellPaysOnePercentToBurnVault() (gas: 0)\n[SKIP] testForkBuyBothShapes() (gas: 0)\n[SKIP] testForkBuybackBurnsThroughTheRealManager() (gas: 0)\n[SKIP] testForkRealFwaRefusesWalletToRouterTransfer() (gas: 0)\n[SKIP] testForkTreasuryRedemptionDeliversRealFwa() (gas: 0)\n[SKIP] testForkUnblessedExactOutputSell() (gas: 0)\n[SKIP] testForkUnblessedSellPaysStakersWhoClaimRealFwa() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 2.06ms (5.50ms CPU time)\n\nRan 22 tests for test/HookEdge.t.sol:ReverseOrderingHookEdgeTest\n[PASS] testAfterSwapRejectsOtherPool() (gas: 55668)\n[PASS] testAfterSwapWithoutBeforeSwapRejected() (gas: 55670)\n[PASS] testBeforeSwapRejectsFeeAboveInt128() (gas: 114605)\n[PASS] testBeforeSwapRejectsZeroAndMinimumAmounts() (gas: 89049)\n[PASS] testBuybackCooldownAndBudgetBoundaries() (gas: 1816298)\n[PASS] testBuybackFloorBlocksAfterPumpThenRecoversWhenHistoryCatchesUp() (gas: 1621853)\n[PASS] testBuybackInsideAnotherUnlockReverts() (gas: 1088858)\n[PASS] testBuybackRejectsBudgetAboveBalance() (gas: 820631)\n[PASS] testConstructorRejectsBadConfigurationBeforeFlags() (gas: 6701)\n[PASS] testDustSwapsThatProduceNothingRevert() (gas: 236796)\n[PASS] testEmptyPoolBuyRevertsWithoutCollectingAFee() (gas: 1227484)\n[PASS] testFeeCollectedEventCarriesTheRate() (gas: 426592)\n[PASS] testFreshHookRefusesEveryWrongPoolAndThenOpensTheRightOne() (gas: 15758216)\n[PASS] testHookUnlockCallbackOnlyDuringRedemption() (gas: 37728)\n[PASS] testObservationRingNeverLosesAThirtyMinuteSample() (gas: 29066851)\n[PASS] testOutsiderCannotMoveVaultOrStakingClaims() (gas: 1406171)\n[PASS] testRedeemTreasuryInsideAnotherUnlockReverts() (gas: 846837)\n[PASS] testRouterCallbackRejectsManagerWithoutCommitment() (gas: 39904)\n[PASS] testRouterExactOutputNeedsFullFillAndEnoughInput() (gas: 697142)\n[PASS] testRouterRejectsMalformedTrades() (gas: 175445)\n[PASS] testRouterSwapInsideAnotherUnlockReverts() (gas: 820468)\n[PASS] testVaultCallbackRejectsManagerWithoutCommitment() (gas: 40720)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 60.07ms (39.36ms CPU time)\n\nRan 15 tests for test/StakingEdge.t.sol:StakingEdgeTest\n[PASS] testClaimWithNothingOwedIsSilent() (gas: 59354)\n[PASS] testDonatedPrayIsNotAStakeAndCannotBeWithdrawn() (gas: 293758)\n[PASS] testDustStreamIsStuckUntilItEndsThenReleasesWhole() (gas: 692695)\n[PASS] testEightStakersProportionalAndSolvent() (gas: 3354662)\n[PASS] testEqualStakersSplitWithinOneWeiEach() (gas: 723590)\n[PASS] testFuzzTwoStakersNeverOverdraw(uint96,uint96,uint96) (runs: 512, μ: 1039937, ~: 1044906)\nLogs:\n  Bound result 162514264337593543950335\n  Bound result 146374512629666717685\n  Bound result 150506556919\n\n[PASS] testGiantRewardOnOneWeiThenLargeStakeKeepsAccounting() (gas: 1359022)\n[PASS] testHeldTaxAccumulatesAcrossEmptyPeriodsAndStreamsOnce() (gas: 1323350)\n[PASS] testLeavingInTheNextBlockAtTheSameTimestampEarnsNothingFromTheStream() (gas: 732058)\n[PASS] testNotifyZeroFromHookIsANoop() (gas: 206866)\n[PASS] testOneWeiStakeEarnsAndClaims() (gas: 755624)\n[PASS] testPendingViewEqualsWhatClaimPaysMidStream() (gas: 1078993)\n[PASS] testStakeRequiresApprovalAndBalance() (gas: 408144)\n[PASS] testStakerCannotUnstakeMoreThanStakedEvenWithDonatedBalance() (gas: 229123)\n[PASS] testTopUpLocksTheWholePositionForOneBlock() (gas: 359900)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 333.87ms (332.12ms CPU time)\n\nRan 12 tests for test/RegistryEdge.t.sol:RegistryEdgeTest\n[PASS] testAnsweredAndExpiredPleasAreFinal() (gas: 2710762)\n[PASS] testApprovalSurvivesSellerMovingTokensAway() (gas: 1737997)\n[PASS] testBlessedSellBoundaryExactAmountAndOneWeiOver() (gas: 3286644)\n[PASS] testBlessingWindowBoundaries() (gas: 1671333)\n[PASS] testDenialNeverStartsTheTwentyFourHourClock() (gas: 3279398)\n[PASS] testEveryAttestationFieldAtItsBoundary() (gas: 2767837)\n[PASS] testExactOutputBlessedSellConsumesExactlyThePrayPaid() (gas: 2142041)\n[PASS] testExpiryBoundaryIsExactlyTwentyFourHours() (gas: 1620754)\n[PASS] testFilingNeedsImdBalanceAndAllowanceAndTenPercentRule() (gas: 3527662)\n[PASS] testPublicSubmitFrontRunsIntakeHarmlessly() (gas: 1640466)\n[PASS] testSleepBoundaryIsExactlyTwentyFourHoursAfterTheLastAnswer() (gas: 1427527)\n[PASS] testWalletsAreIndependent() (gas: 2542707)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 339.21ms (32.75ms CPU time)\n\nRan 15 tests for test/Registry.t.sol:RegistryTest\n[PASS] testCallbackUnknownRequestAndUnauthorized() (gas: 1498533)\n[PASS] testCallbackWithinStipendAndPublicFallback() (gas: 2551614)\n[PASS] testEscapedTextIsValidJson() (gas: 1397628)\n[PASS] testExpirePendingNoRefundAndWakeOnNextAnswer() (gas: 2876156)\n[PASS] testExpiredAndFutureAttestations() (gas: 1516753)\n[PASS] testFalseAnswerStoresVerdictAndDoesNotBless() (gas: 1540611)\n[PASS] testFilePleaPaysAndPinsTemplate() (gas: 1506099)\n[PASS] testFuzzJsonEscapeRoundTrip(bytes) (runs: 128, μ: 49598, ~: 54000)\n[PASS] testMaxEscapedTextCallbackUnder200k() (gas: 5787979)\n[PASS] testOneOpenAndOneBlessingPer24Hours() (gas: 2535644)\n[PASS] testOnlyHookCanConsume() (gas: 1681716)\n[PASS] testPleaSizeLimitBalanceAndUtf8() (gas: 1567901)\n[PASS] testReplayAndWrongPleaCannotBorrowApproval() (gas: 2534426)\n[PASS] testWrongSignerAndDomain() (gas: 1603646)\n[PASS] testWrongTypeChainPanelAndWindow() (gas: 1807997)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 372.26ms (46.25ms CPU time)\n\nRan 25 tests for test/Staking.t.sol:StakingTest\n[PASS] testAccrualAndClaimPaysFwaFromManager() (gas: 867062)\n[PASS] testBlessedSellGoesToBurnVault() (gas: 1955232)\n[PASS] testBuybackWithoutArgumentUsesBalanceCapped() (gas: 857463)\n[PASS] testDustStakerCannotOverdraw() (gas: 1285684)\n[PASS] testEveryoneLeavesMidStreamRemainderHeldAgain() (gas: 1091040)\n[PASS] testFlashStakeAroundOwnSellCannotUnstakeSameBlock() (gas: 817456)\n[PASS] testFuzzSinnerSplitConservation(uint96) (runs: 128, μ: 463755, ~: 463958)\nLogs:\n  Bound result 99999999999999030668778\n\n[PASS] testFuzzVaultSolvency(uint256) (runs: 128, μ: 5253297, ~: 5253735)\n[PASS] testLaunchBuyAtFiftyPercentGoesToBurnVaultNotStakers() (gas: 338789)\n[PASS] testLaunchDecayExactPoints() (gas: 68467)\n[PASS] testNoRetroactiveRewards() (gas: 1042611)\n[PASS] testOnlyHookCanNotify() (gas: 55664)\n[PASS] testProportionalAccrual() (gas: 765073)\n[PASS] testRatesAreConstants() (gas: 37770)\n[PASS] testSleepingSellGoesToBurnVault() (gas: 377653)\n[PASS] testStakeAndUnstakeNoLock() (gas: 404056)\n[PASS] testStakeUnstakeValidation() (gas: 356036)\n[PASS] testStreamSharedByLateStakerOnlyFromJoining() (gas: 863546)\n[PASS] testTaxDuringStreamPaidImmediatelyAndStreamContinues() (gas: 944961)\n[PASS] testTaxWithNoStakersIsHeldThenStreamedOverSevenDays() (gas: 1055614)\n[PASS] testTopUpSettlesFirst() (gas: 1130466)\n[PASS] testUnblessedSellExactInputSplitToStakers() (gas: 469531)\n[PASS] testUnblessedSellExactOutputSplitToStakers() (gas: 436453)\n[PASS] testUnlockCallbackRefusesOutsiders() (gas: 66679)\n[PASS] testUnstakeKeepsEarnedRewardsAndStopsAccrual() (gas: 1241224)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 372.26ms (379.08ms CPU time)\n\nRan 9 tests for test/FeeMath.t.sol:ReverseOrderingFeeMathTest\n[PASS] testFuzzBlessingConsumptionIsExactAcrossSplits(uint96,uint96) (runs: 128, μ: 2499449, ~: 2501381)\nLogs:\n  Bound result 3416269050\n  Bound result 1416272552\n\n[PASS] testFuzzBuyExactInputAtAnyMoment(uint96,uint32) (runs: 128, μ: 419454, ~: 423465)\nLogs:\n  Bound result 3501\n  Bound result 3416269050\n\n[PASS] testFuzzBuyExactOutputAtAnyMoment(uint96,uint32) (runs: 128, μ: 447114, ~: 451178)\nLogs:\n  Bound result 3501\n  Bound result 3416269050\n\n[PASS] testFuzzDecayFormulaAndMonotonicity(uint32) (runs: 128, μ: 45477, ~: 45497)\nLogs:\n  Bound result 233\n\n[PASS] testFuzzForeignRouterPaysTheSameFee(uint96,bool) (runs: 128, μ: 461155, ~: 488614)\nLogs:\n  Bound result 9999999999999000009063\n\n[PASS] testFuzzLaunchSellsAreNotDiscountedByTheBuyDecay(uint96,uint32) (runs: 128, μ: 494610, ~: 499433)\nLogs:\n  Bound result 1701\n  Bound result 3416269050\n\n[PASS] testFuzzSellExactInputEveryRateClass(uint96,uint8) (runs: 128, μ: 934873, ~: 499079)\nLogs:\n  Bound result 3416269050\n\n[PASS] testFuzzSellExactOutputEveryRateClass(uint96,uint8) (runs: 128, μ: 961419, ~: 522777)\nLogs:\n  Bound result 3416269050\n\n[PASS] testFuzzTreasuryRedemptionMatchesTenPercentOfEveryFee(uint96,uint96) (runs: 128, μ: 809964, ~: 810471)\nLogs:\n  Bound result 3416269050\n  Bound result 49999999999999000003502\n\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 384.69ms (729.75ms CPU time)\n\nRan 22 tests for test/HookEdge.t.sol:HookEdgeTest\n[PASS] testAfterSwapRejectsOtherPool() (gas: 55668)\n[PASS] testAfterSwapWithoutBeforeSwapRejected() (gas: 55670)\n[PASS] testBeforeSwapRejectsFeeAboveInt128() (gas: 114605)\n[PASS] testBeforeSwapRejectsZeroAndMinimumAmounts() (gas: 89049)\n[PASS] testBuybackCooldownAndBudgetBoundaries() (gas: 1816298)\n[PASS] testBuybackFloorBlocksAfterPumpThenRecoversWhenHistoryCatchesUp() (gas: 1621853)\n[PASS] testBuybackInsideAnotherUnlockReverts() (gas: 1088858)\n[PASS] testBuybackRejectsBudgetAboveBalance() (gas: 820631)\n[PASS] testConstructorRejectsBadConfigurationBeforeFlags() (gas: 6701)\n[PASS] testDustSwapsThatProduceNothingRevert() (gas: 236796)\n[PASS] testEmptyPoolBuyRevertsWithoutCollectingAFee() (gas: 1227484)\n[PASS] testFeeCollectedEventCarriesTheRate() (gas: 426592)\n[PASS] testFreshHookRefusesEveryWrongPoolAndThenOpensTheRightOne() (gas: 15758216)\n[PASS] testHookUnlockCallbackOnlyDuringRedemption() (gas: 37728)\n[PASS] testObservationRingNeverLosesAThirtyMinuteSample() (gas: 29066851)\n[PASS] testOutsiderCannotMoveVaultOrStakingClaims() (gas: 1406171)\n[PASS] testRedeemTreasuryInsideAnotherUnlockReverts() (gas: 846837)\n[PASS] testRouterCallbackRejectsManagerWithoutCommitment() (gas: 39904)\n[PASS] testRouterExactOutputNeedsFullFillAndEnoughInput() (gas: 697142)\n[PASS] testRouterRejectsMalformedTrades() (gas: 175445)\n[PASS] testRouterSwapInsideAnotherUnlockReverts() (gas: 820468)\n[PASS] testVaultCallbackRejectsManagerWithoutCommitment() (gas: 40720)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 387.91ms (52.14ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:ReverseOrderingHookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980258)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3865759, ~: 3867562)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378573, ~: 378693)\nLogs:\n  Bound result 30668777\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985389)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905851)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 468.46ms (515.25ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:HookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980246)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3864040, ~: 3867238)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378566, ~: 378693)\nLogs:\n  Bound result 30668777\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985353)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905815)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 468.52ms (330.91ms CPU time)\n\nRan 9 tests for test/FeeMath.t.sol:FeeMathTest\n[PASS] testFuzzBlessingConsumptionIsExactAcrossSplits(uint96,uint96) (runs: 1000, μ: 2499718, ~: 2501375)\nLogs:\n  Bound result 99999999999998000012656\n  Bound result 99999999999996000021226\n\n[PASS] testFuzzBuyExactInputAtAnyMoment(uint96,uint32) (runs: 1000, μ: 419024, ~: 423465)\nLogs:\n  Bound result 1368\n  Bound result 99999999999999000012656\n\n[PASS] testFuzzBuyExactOutputAtAnyMoment(uint96,uint32) (runs: 1000, μ: 446699, ~: 451178)\nLogs:\n  Bound result 1799\n  Bound result 99999999999999000004874\n\n[PASS] testFuzzDecayFormulaAndMonotonicity(uint32) (runs: 1000, μ: 45495, ~: 45497)\nLogs:\n  Bound result 0\n\n[PASS] testFuzzForeignRouterPaysTheSameFee(uint96,bool) (runs: 1000, μ: 456506, ~: 488614)\nLogs:\n  Bound result 9999999999999000001573\n\n[PASS] testFuzzLaunchSellsAreNotDiscountedByTheBuyDecay(uint96,uint32) (runs: 1000, μ: 493682, ~: 499421)\nLogs:\n  Bound result 1799\n  Bound result 9999999999999000004874\n\n[PASS] testFuzzSellExactInputEveryRateClass(uint96,uint8) (runs: 1000, μ: 923934, ~: 499022)\nLogs:\n  Bound result 99999999999999000012656\n\n[PASS] testFuzzSellExactOutputEveryRateClass(uint96,uint8) (runs: 1000, μ: 950296, ~: 522738)\nLogs:\n  Bound result 9999999999999470282289\n\n[PASS] testFuzzTreasuryRedemptionMatchesTenPercentOfEveryFee(uint96,uint96) (runs: 1000, μ: 809767, ~: 810445)\nLogs:\n  Bound result 49999999999999000004874\n  Bound result 49999999999999000017314\n\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 468.47ms (3.28s CPU time)\n\nRan 1 test for test/invariants/StakingInvariant.t.sol:StakingInvariantTest\n[PASS]\nStakingInvariantTest invariants:\n[PASS] invariant_feeBookkeeping\n[PASS] invariant_fwaClaimsAreConserved\n[PASS] invariant_prayCustody\n[PASS] invariant_solvent\n[PASS] invariant_streamStateMachine\n StakingInvariantTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭----------------+------------------+-------+---------+----------╮\n| Contract       | Selector         | Calls | Reverts | Discards |\n+================================================================+\n| StakingHandler | buy              | 147   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | claim            | 183   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | donateClaims     | 150   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | donatePray       | 158   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | roll             | 192   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | sin              | 139   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | sinExactOutput   | 162   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | stake            | 153   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | stakeAllHoldings | 176   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | unstake          | 156   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | unstakeAll       | 159   | 0       | 0        |\n|----------------+------------------+-------+---------+----------|\n| StakingHandler | warp             | 145   | 0       | 0        |\n╰----------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 109620\n  Bound result 620653945036178778331061\n  Bound result 10000000000000000000000000\n  Bound result 371636862\n  Bound result 5789317173834950503092466\n  Bound result 19999999000000000002343\n  Bound result 3609573737135686404102011\n  Bound result 18019\n  Bound result 5000000000000000000000000\n  Bound result 2237\n  Bound result 86399\n  Bound result 2\n  Bound result 185290658398785643930\n  Bound result 12345000000000000000000\n  Bound result 12056438173349515121076\n  Bound result 19999999000000000011563\n  Bound result 5000000000000000000000000\n  Bound result 10031601294180165200644377\n  Bound result 1800\n  Bound result 19999999000000000004887\n  Bound result 19535592640595176969027\n  Bound result 900000000000000000000\n  Bound result 14056\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 753.11ms (744.44ms CPU time)\n\nRan 1 test for test/invariants/FeeInvariant.t.sol:FeeInvariantTest\n[PASS]\nFeeInvariantTest invariants:\n[PASS] invariant_blessingsNeverExceedGrant\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_claimsMatchCounters\n[PASS] invariant_feeRateBounds\n[PASS] invariant_feeSplitIdentity\n[PASS] invariant_nothingStranded\n[PASS] invariant_supplyOnlyFallsByBurns\n FeeInvariantTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭------------+----------------------+-------+---------+----------╮\n| Contract   | Selector             | Calls | Reverts | Discards |\n+================================================================+\n| FeeHandler | addLiquidity         | 121   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | bless                | 112   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | blessAndSell         | 121   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | blessAndSellExactOut | 113   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | buyExactIn           | 142   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | buyExactOut          | 117   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | buyback              | 121   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | deny                 | 135   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | expireStale          | 129   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | fileAndAbandon       | 115   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | foreignSell          | 117   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | redeemTreasury       | 113   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | roll                 | 116   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | sellExactIn          | 129   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | sellExactOut         | 105   | 0       | 0        |\n|------------+----------------------+-------+---------+----------|\n| FeeHandler | warp                 | 114   | 0       | 0        |\n╰------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 999999999000000000000241\n  Bound result 1142994552205921325685\n  Bound result 49999999999000000001235\n  Bound result 19703990873672423076549\n  Bound result 49999999999002835717308\n  Bound result 19999999999000000000051\n  Bound result 19999999999000000005295\n  Bound result 997999999000100000005227\n  Bound result 4999999999000000001000\n  Bound result 49998999998000000002257\n  Bound result 49998999998000000002258\n  Bound result 49999999999000000005186\n  Bound result 16463\n  Bound result 49999999999000000000649\n  Bound result 4999999999000000001001\n  Bound result 4999999000000000000012501\n  Bound result 2000000000000\n  Bound result 137594472132646299091391\n  Bound result 4999999999000000008005\n  Bound result 633305721688604129\n  Bound result 978848822456808209662330\n  Bound result 1855865141931126473022\n  Bound result 21872050806563059034921\n  Bound result 11000000000000000000\n  Bound result 24577\n  Bound result 49999999999000000015891\n  Bound result 10801922683784429656574\n  Bound result 49998999998000000010881\n  Bound result 10905127610656710656914\n  Bound result 15025\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.13s (1.12s CPU time)\n\nRan 14 test suites in 1.14s (5.54s CPU time): 189 tests passed, 0 failed, 7 skipped (196 total tests)\n","passed":true},{"durationMs":103,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BurnVault.buyback()\",\"BurnVault.buyback(uint256)\",\"BurnVault.unlockCallback(bytes)\",\"Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Hook.redeemTreasury()\",\"Hook.unlockCallback(bytes)\",\"PleaRegistry.consume(address,uint256)\",\"PleaRegistry.expire(uint256)\",\"PleaRegistry.filePlea(uint256,string)\",\"PleaRegistry.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PleaRegistry.submit(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PrayRouter.swap((bool,int256,uint256,uint256,uint160,uint256))\",\"PrayRouter.unlockCallback(bytes)\",\"StakingVault.claim()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.unlockCallback(bytes)\",\"StakingVault.unstake(uint256)\",\"Token.approve(address,uint256)\",\"Token.burn(uint256)\",\"Token.transfer(address,uint256)\",\"Token.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":142,\"deployment-parameters.json\":40,\"docs/REVIEW.md\":75,\"docs/VALIDATION.md\":54,\"fork/Mainnet.t.sol\":72,\"foundry.toml\":19,\"remappings.txt\":4,\"scripts/check-build.mjs\":15,\"scripts/check-oracle.mjs\":11,\"scripts/configure-site.mjs\":18,\"scripts/mine-hook.mjs\":9,\"site/app.js\":66,\"site/deployment.json\":7,\"site/index.html\":24,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/BurnVault.sol\":94,\"src/Hook.sol\":301,\"src/HookFlags.sol\":30,\"src/IPrayPool.sol\":10,\"src/OracleAttestation.sol\":239,\"src/PleaRegistry.sol\":208,\"src/PleaText.sol\":146,\"src/PrayRouter.sol\":84,\"src/StakingVault.sol\":199,\"src/Token.sol\":14,\"test/FeeMath.t.sol\":230,\"test/Hook.t.sol\":376,\"test/HookEdge.t.sol\":436,\"test/MainnetFork.t.sol\":144,\"test/OracleConformance.t.sol\":100,\"test/Registry.t.sol\":271,\"test/RegistryEdge.t.sol\":311,\"test/Staking.t.sol\":444,\"test/StakingEdge.t.sol\":286,\"test/TestBase.sol\":252,\"test/fixtures/live-attestation.json\":1,\"test/fixtures/live-request.json\":1,\"test/invariants/FeeInvariant.t.sol\":655,\"test/invariants/StakingInvariant.t.sol\":484,\"test/mocks/MockERC20.sol\":29,\"test/mocks/MockIntake.sol\":36,\"test/site/quote-race.mjs\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"24842f40b077560676f196f8f29b91b6066c92b7319e0a290159d97e4afa2306","verifiedTreeHash":"fc6df00bd3f3fd55df02502f4d7105834a836631","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":51976,"exitCode":0,"name":"build","output":"Compiling 108 files with Solc 0.8.26\nSolc 0.8.26 finished in 51.63s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/Registry.t.sol:224:5:\n    |\n224 |     function testFuzzJsonEscapeRoundTrip(bytes calldata raw) public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/PrayRouter.sol:64:16\n   │\n64 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:31:17\n   │\n31 │                 revert InvalidText();\n   │                 ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:33:36\n   │\n33 │             if (i + n >= s.length) revert InvalidText();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:36:43\n   │\n36 │                 if (d < 0x80 || d > 0xbf) revert InvalidText();\n   │                                           ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:42:15\n   │\n42 │             ) revert InvalidText();\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/PrayRouter.sol:42:13\n   │\n42 │             block.timestamp > trade.deadline || trade.maxInput == 0\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaRegistry.sol:29:38\n   │\n29 │     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/PrayRouter.sol:56:9\n   │\n56 │         emit Traded(msg.sender, trade.buy, paid, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:62:27\n   │\n62 │                         ? bytes1(\"b\")\n   │                           ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaRegistry.sol:83:16\n   │\n83 │         return block.timestamp >= lastAnswer + 24 hours;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:63:36\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaRegistry.sol:88:16\n   │\n88 │         return block.timestamp < b.until ? b.remaining : 0;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:43:37\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 0\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/PrayRouter.sol:43:45\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 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[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:63:60\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                            ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:84\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:98\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                                  ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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-events-access-control]: `active` is changed without an event but is used for access control\n    ╭▸ src/StakingVault.sol:159:16\n    │\n159 │         delete active;\n    │                ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:107:16\n    │\n107 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n108 │ ┃             '{\"answerType\":\"bool\",\"chainId\":1,\"definitions\":{\"rubric\":\"',\n109 │ ┃             RUBRIC,\n110 │ ┃             '\"},\"evidence\":\"panel\",\"question\":\"',\n111 │ ┃             escape(q),\n112 │ ┃             '\",\"v\":1,\"window\":'\n113 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PleaRegistry.sol:96:43\n   │\n96 │                 || (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours)\n   │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:117:16\n    │\n117 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n118 │ ┃             p,\n119 │ ┃             '{\"hours\":1},\"panelSize\":7,\"quorum\":5,\"validForSeconds\":900,\"consumer\":{\"chainId\":1,\"verifyingContract\":\"',\n120 │ ┃             Strings.toHexString(registry),\n121 │ ┃             '\"}}'\n122 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:74:69\n   │\n74 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\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/StakingVault.sol:74:55\n   │\n74 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\n   │                                                       ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:78:24\n   │\n78 │         uint256 paid = uint256(-int256(inputDelta));\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/PrayRouter.sol:79:28\n   │\n79 │         uint256 received = uint256(int256(outputDelta));\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/PleaRegistry.sol:122:9\n    │\n122 │         emit PleaFiled(id, msg.sender, requestId, amount, text);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:127:13\n    │\n127 │ ┏             abi.encodePacked(\n128 │ ┃                 p,\n129 │ ┃                 '{\"fromBlock\":',\n130 │ ┃                 Strings.toString(fromBlock),\n    ‡ ┃\n133 │ ┃                 \"}}\"\n134 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:82:58\n   │\n82 │                 || (t.amountSpecified > 0 && received != uint256(t.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/PleaRegistry.sol:103:13\n    │\n103 │             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-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/BurnVault.sol:68:16\n   │\n68 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/BurnVault.sol:45:50\n   │\n45 │         if (budget == 0 || budget > MAX_SPEND || block.timestamp < lastBuyback + 60) revert InvalidBuyback();\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/BurnVault.sol:61:9\n   │\n61 │         emit BoughtAndBurned(msg.sender, spend, reward, burned);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:77:18\n   │\n77 │                 -int256(spend),\n   │                  ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:23\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\n   │                       ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:55\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\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/BurnVault.sol:89:26\n   │\n89 │         uint256 burned = uint256(int256(output));\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/StakingVault.sol:93:13\n   │\n93 │             emit RewardReceived(amount, true);\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/StakingVault.sol:265:9\n    │\n265 │         emit StreamStarted(streamRemaining, streamEnd);\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/StakingVault.sol:99:13\n   │\n99 │             emit RewardReceived(amount, false);\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/StakingVault.sol:120:9\n    │\n120 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:139:9\n    │\n139 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:152:9\n    │\n152 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/StakingVault.sol:151:9\n    │\n151 │         manager.unlock(data);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:198:13\n    │\n198 │         if (streamEnd <= block.timestamp || earning == 0) return 0;\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/PleaRegistry.sol:162:43\n    │\n162 │         if (p.status != Status.Pending || block.timestamp >= uint256(p.filedAt) + PENDING_LIFETIME) {\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/PleaRegistry.sol:180:39\n    │\n180 │             if (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours) {\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:214:53\n    │\n214 │         if (streamRemaining == 0 || earning == 0 || block.timestamp <= lastUpdate) return 0;\n    │                                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:215:13\n    │\n215 │         if (block.timestamp >= streamEnd) return streamRemaining;\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/PleaRegistry.sol:187:9\n    │\n187 │ ┏         emit PleaAnswered(\n188 │ ┃             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\n189 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:222:13\n    │\n222 │         if (released > 0) {\n    │             ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:184:23\n    │\n184 │             b.until = uint64(block.timestamp + 15 minutes);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:185:29\n    │\n185 │             b.lastGranted = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:188:50\n    │\n188 │             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\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/PleaRegistry.sol:194:43\n    │\n194 │         if (p.status != Status.Pending || block.timestamp < uint256(p.filedAt) + PENDING_LIFETIME) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-events-arithmetic]: `cumulative` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:277:9\n    │\n277 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observedAt` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:278:9\n    │\n278 │         observedAt = uint64(block.timestamp);\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:234:9\n    │\n234 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:129:9\n    │\n129 │         emit PoolOpened(PoolId.unwrap(poolId), address(registry), address(router), address(vault));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:126:22\n    │\n126 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:128:39\n    │\n128 │         observations[0] = Observation(uint64(block.timestamp), 0);\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/Hook.sol:136:13\n    │\n136 │             elapsed >= DECAY ? LOW_FEE_BPS : LAUNCH_FEE_BPS - (LAUNCH_FEE_BPS - LOW_FEE_BPS) * elapsed / DECAY;\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/Hook.sol:249:9\n    │\n249 │         emit FeeCollected(seller, buy, fee, vaultPart, treasuryPart, rate);\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/Hook.sol:254:13\n    │\n254 │             emit SinnersTax(seller, fee, vaultPart, treasuryPart);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:177:56\n    │\n177 │                 uint256 most = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : maxInput;\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/Hook.sol:190:54\n    │\n190 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:190:84\n    │\n190 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:194:23\n    │\n194 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:194:31\n    │\n194 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:200:63\n    │\n200 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/Hook.sol:200:70\n    │\n200 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:215:34\n    │\n215 │         address seller = address(uint160(_load(SELLER)));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/Hook.sol:217:67\n    │\n217 │         if (!buy && _load(BLESSED) != 0) registry.consume(seller, uint256(-int256(prayDelta)));\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/Hook.sol:222:51\n    │\n222 │             int256 expected = p.amountSpecified + int256(_load(UPFRONT));\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:225:36\n    │\n225 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:225:65\n    │\n225 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:229:23\n    │\n229 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:229:31\n    │\n229 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:236:44\n    │\n236 │         return (IHooks.afterSwap.selector, int128(int256(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/Hook.sol:236:51\n    │\n236 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                   ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:271:9\n    │\n271 │         emit TreasuryPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Hook.sol:279:13\n    │\n279 │         if (block.timestamp >= uint256(observations[observationIndex].time) + 60) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:277:23\n    │\n277 │         cumulative += int128(int256(lastTick) * int256(elapsed));\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/Hook.sol:277:49\n    │\n277 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │                                                 ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:278:22\n    │\n278 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:281:58\n    │\n281 │             observations[observationIndex] = Observation(uint64(block.timestamp), cumulative);\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/Hook.sol:286:29\n    │\n286 │         if (!initialized || block.timestamp < launchedAt + 30 minutes) revert HistoryTooShort();\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/Hook.sol:290:38\n    │\n290 │             if (o.time > old.time && uint256(o.time) + 30 minutes <= block.timestamp) old = o;\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:293:66\n    │\n293 │         int256 current = int256(cumulative) + int256(lastTick) * int256(block.timestamp - observedAt);\n    │                                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:296:37\n    │\n296 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:296:45\n    │\n296 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\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/Hook.sol:296:93\n    │\n296 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                                                                             ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:297:52\n    │\n297 │         uint160 sqrt = TickMath.getSqrtPriceAtTick(int24(average));\n    │                                                    ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:87:9\n    │\n 87 │         vm.warp(hook.launchedAt() + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    ‡\n250 │         vm.warp(hook.launchedAt() + 1800);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:192:9\n    │\n192 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:192:9\n    │\n192 │         vm.warp(block.timestamp + 900);\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/Hook.t.sol:192:17\n    │\n192 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:196:9\n    │\n196 │         vm.warp(registry.lastAnswer() + 86400);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/Registry.t.sol:94:17\n   │\n94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:178:17\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:238:17\n    │\n238 │         vm.warp(block.timestamp + 86400);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Staking.t.sol:359:9\n    │\n359 │         vm.warp(vm.getBlockTimestamp() + 1 days);\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":401,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testProtocolDigestSignatureAndSelector() (gas: 713241)\n[PASS] testRealMainnetSignerAttestation() (gas: 3350374)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.54ms (1.94ms CPU time)\n\nRan 15 tests for test/Registry.t.sol:RegistryTest\n[PASS] testCallbackUnknownRequestAndUnauthorized() (gas: 1498545)\n[PASS] testCallbackWithinStipendAndPublicFallback() (gas: 2551638)\n[PASS] testEscapedTextIsValidJson() (gas: 1397628)\n[PASS] testExpirePendingNoRefundAndWakeOnNextAnswer() (gas: 2891933)\n[PASS] testExpiredAndFutureAttestations() (gas: 1516765)\n[PASS] testFalseAnswerStoresVerdictAndDoesNotBless() (gas: 1540611)\n[PASS] testFilePleaPaysAndPinsTemplate() (gas: 1506099)\n[PASS] testFuzzJsonEscapeRoundTrip(bytes) (runs: 128, μ: 49070, ~: 54225)\n[PASS] testMaxEscapedTextCallbackUnder200k() (gas: 5788003)\n[PASS] testOneOpenAndOneBlessingPer24Hours() (gas: 2535656)\n[PASS] testOnlyHookCanConsume() (gas: 1681728)\n[PASS] testPleaSizeLimitBalanceAndUtf8() (gas: 1567901)\n[PASS] testReplayAndWrongPleaCannotBorrowApproval() (gas: 2534462)\n[PASS] testWrongSignerAndDomain() (gas: 1603694)\n[PASS] testWrongTypeChainPanelAndWindow() (gas: 1808081)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 206.36ms (37.94ms CPU time)\n\nRan 33 tests for test/Staking.t.sol:StakingTest\n[PASS] testAccrualAndClaimPaysFwaFromManager() (gas: 1021784)\n[PASS] testBlessedSellGoesToBurnVault() (gas: 1955588)\n[PASS] testBuybackWithoutArgumentUsesBalanceCapped() (gas: 857655)\n[PASS] testDustStakerCannotOverdraw() (gas: 1488994)\n[PASS] testEveryoneLeavesMidStreamRemainderHeldAgain() (gas: 1240315)\n[PASS] testFlashStakeAroundOwnSellCannotUnstakeSameBlock() (gas: 1070687)\n[PASS] testFuzzExclusionAndWarmupStaySolvent(uint256) (runs: 128, μ: 5497361, ~: 5452183)\n[PASS] testFuzzSinnerSplitConservation(uint96) (runs: 128, μ: 479617, ~: 479811)\nLogs:\n  Bound result 44172242989664362842471\n\n[PASS] testFuzzVaultSolvency(uint256) (runs: 128, μ: 5965777, ~: 5934806)\n[PASS] testHeldTaxStreamsToWarmingStakerFromNextBlock() (gas: 804092)\n[PASS] testLaunchBuyAtFiftyPercentGoesToBurnVaultNotStakers() (gas: 339094)\n[PASS] testLaunchDecayExactPoints() (gas: 68621)\n[PASS] testNoRetroactiveRewards() (gas: 1232105)\n[PASS] testOnlyHookCanNotify() (gas: 59111)\n[PASS] testProportionalAccrual() (gas: 943086)\n[PASS] testRatesAreConstants() (gas: 38100)\n[PASS] testSellerAsOnlyStakerTaxIsStreamedNotPaidAtOnce() (gas: 1208744)\n[PASS] testSellerKeepsEarningFromOthersAfterOwnSell() (gas: 1277188)\n[PASS] testSellerStakedAheadEarnsNothingFromOwnSell() (gas: 1254970)\n[PASS] testSleepingSellGoesToBurnVault() (gas: 372684)\n[PASS] testStakeAndUnstakeNoLock() (gas: 495212)\n[PASS] testStakeInSellBlockEarnsNothingFromThatSell() (gas: 1593027)\n[PASS] testStakeUnstakeValidation() (gas: 453035)\n[PASS] testStreamSharedByLateStakerOnlyFromJoining() (gas: 1012313)\n[PASS] testTaxDuringStreamPaidImmediatelyAndStreamContinues() (gas: 1037178)\n[PASS] testTaxWithNoStakersIsHeldThenStreamedOverSevenDays() (gas: 1240170)\n[PASS] testTopUpSettlesFirst() (gas: 1347968)\n[PASS] testUnblessedSellExactInputSplitToStakers() (gas: 485560)\n[PASS] testUnblessedSellExactOutputSplitToStakers() (gas: 452321)\n[PASS] testUnlockCallbackRefusesOutsiders() (gas: 66811)\n[PASS] testUnstakeActivePartWhileNewStakeIsWarming() (gas: 1107059)\n[PASS] testUnstakeKeepsEarnedRewardsAndStopsAccrual() (gas: 1432644)\n[PASS] testWarmingStakeStartsAtItsBlocksClosingAccumulator() (gas: 1641342)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 206.48ms (431.58ms CPU time)\n\nRan 31 tests for test/Hook.t.sol:ReverseOrderingHookTest\n[PASS] testBlessedExactOutput() (gas: 1931634)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2477232)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 2002190)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819743)\n[PASS] testBuyExactOutputAndRefund() (gas: 375669)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1247029)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462641)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 821127)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1110272)\n[PASS] testContractCalledByBlessedWalletCannotSellOnItsBlessing() (gas: 2542238)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964333)\n[PASS] testExactOutputBlessedOnlyWhenMaxInputFits() (gas: 2278802)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2297807)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1990920)\n[PASS] testForeignRouterIsNeverBlessedEvenForBlessedOrigin() (gas: 2282336)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3957697, ~: 3955982)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378725, ~: 378822)\nLogs:\n  Bound result 4172242988259556829696\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 17020)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705589)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 2001018)\n[PASS] testOverAmountExactOutputPaysHighAndKeepsBlessing() (gas: 2009579)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327620)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78763)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397588)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 402046)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541153)\n[PASS] testSupplyTransferAndBurn() (gas: 134162)\n[PASS] testUnauthorizedCallbacks() (gas: 181276)\n[PASS] testUnblessedSellExactInput() (gas: 433276)\n[PASS] testUnblessedSellExactOutput() (gas: 436052)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 268.46ms (265.04ms CPU time)\n\nRan 31 tests for test/Hook.t.sol:HookTest\n[PASS] testBlessedExactOutput() (gas: 1931622)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2477220)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 2002178)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819743)\n[PASS] testBuyExactOutputAndRefund() (gas: 375669)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1247029)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462641)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 821127)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1110272)\n[PASS] testContractCalledByBlessedWalletCannotSellOnItsBlessing() (gas: 2542226)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964333)\n[PASS] testExactOutputBlessedOnlyWhenMaxInputFits() (gas: 2278790)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2297795)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1990932)\n[PASS] testForeignRouterIsNeverBlessedEvenForBlessedOrigin() (gas: 2282324)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3961470, ~: 3960666)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378730, ~: 378822)\nLogs:\n  Bound result 40986261\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33202)\n[PASS] testInitializationAndFlags() (gas: 17020)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705589)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 2001030)\n[PASS] testOverAmountExactOutputPaysHighAndKeepsBlessing() (gas: 2009591)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327620)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78763)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397588)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 402046)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541153)\n[PASS] testSupplyTransferAndBurn() (gas: 134162)\n[PASS] testUnauthorizedCallbacks() (gas: 181276)\n[PASS] testUnblessedSellExactInput() (gas: 433276)\n[PASS] testUnblessedSellExactOutput() (gas: 436052)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 268.68ms (332.96ms CPU time)\n\nRan 5 test suites in 269.71ms (951.51ms CPU time): 112 tests passed, 0 failed, 0 skipped (112 total tests)\n","passed":true},{"durationMs":83,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BurnVault.buyback()\",\"BurnVault.buyback(uint256)\",\"BurnVault.unlockCallback(bytes)\",\"Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Hook.redeemTreasury()\",\"Hook.unlockCallback(bytes)\",\"PleaRegistry.consume(address,uint256)\",\"PleaRegistry.expire(uint256)\",\"PleaRegistry.filePlea(uint256,string)\",\"PleaRegistry.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PleaRegistry.submit(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PrayRouter.swap((bool,int256,uint256,uint256,uint160,uint256))\",\"PrayRouter.unlockCallback(bytes)\",\"StakingVault.claim()\",\"StakingVault.notifyReward(uint256,address)\",\"StakingVault.stake(uint256)\",\"StakingVault.unlockCallback(bytes)\",\"StakingVault.unstake(uint256)\",\"Token.approve(address,uint256)\",\"Token.burn(uint256)\",\"Token.transfer(address,uint256)\",\"Token.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":142,\"deployment-parameters.json\":40,\"docs/REVIEW.md\":90,\"docs/VALIDATION.md\":63,\"fork/Mainnet.t.sol\":74,\"foundry.toml\":19,\"remappings.txt\":4,\"scripts/check-build.mjs\":15,\"scripts/check-oracle.mjs\":11,\"scripts/configure-site.mjs\":18,\"scripts/mine-hook.mjs\":9,\"site/app.js\":66,\"site/deployment.json\":7,\"site/index.html\":24,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/BurnVault.sol\":94,\"src/Hook.sol\":312,\"src/HookFlags.sol\":30,\"src/IPrayPool.sol\":10,\"src/OracleAttestation.sol\":239,\"src/PleaRegistry.sol\":208,\"src/PleaText.sol\":146,\"src/PrayRouter.sol\":88,\"src/StakingVault.sol\":267,\"src/Token.sol\":14,\"test/Hook.t.sol\":462,\"test/OracleConformance.t.sol\":100,\"test/Registry.t.sol\":271,\"test/Staking.t.sol\":657,\"test/TestBase.sol\":252,\"test/fixtures/live-attestation.json\":1,\"test/fixtures/live-request.json\":1,\"test/mocks/MockERC20.sol\":29,\"test/mocks/MockIntake.sol\":36,\"test/site/quote-race.mjs\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":10818,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/StakingVault.sol:155: StakingVault.unlockCallback(bytes) (src/StakingVault.sol#155-164) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:219: StakingVault._update() (src/StakingVault.sol#219-228) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:209: StakingVault._earning() (src/StakingVault.sol#209-211) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:142: StakingVault.claim() (src/StakingVault.sol#142-153) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaRegistry.sol:125: PleaRegistry.questionPrefix(uint256) (src/PleaRegistry.sol#125-131) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:213: StakingVault._released(uint256) (src/StakingVault.sol#213-217) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:186: StakingVault.earningStakeOf(address) (src/StakingVault.sol#186-188) uses a dangerous strict equality:\n[medium/medium] unused-return at src/StakingVault.sol:142: StakingVault.claim() (src/StakingVault.sol#142-153) ignores return value by manager.unlock(data) (src/StakingVault.sol#151)\n[medium/medium] unused-return at src/Hook.sol:203: Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#203-237) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/Hook.sol#233)\n[low/medium] missing-zero-check at src/StakingVault.sol:74: StakingVault.constructor(IPoolManager,IERC20,address,address).hook_ (src/StakingVault.sol#74) lacks a zero-check on :\n[low/medium] missing-zero-check at src/StakingVault.sol:74: StakingVault.constructor(IPoolManager,IERC20,address,address).fwa_ (src/StakingVault.sol#74) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/BurnVault.sol:44: Reentrancy in BurnVault.buyback(uint256) (src/BurnVault.sol#44-62):\n[low/medium] reentrancy-benign at src/Hook.sol:259: Reentrancy in Hook.redeemTreasury() (src/Hook.sol#259-263):\n[low/medium] reentrancy-benign at src/PleaRegistry.sol:91: Reentrancy in PleaRegistry.filePlea(uint256,string) (src/PleaRegistry.sol#91-123):\n[low/medium] reentrancy-benign at src/PrayRouter.sol:40: Reentrancy in PrayRouter.swap(PrayRouter.Trade) (src/PrayRouter.sol#40-57):\n[low/medium] reentrancy-benign at src/Hook.sol:203: Reentrancy in Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#203-237):\n[low/medium] reentrancy-events at src/Hook.sol:203: Reentrancy in Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#203-237):\n[low/medium] reentrancy-events at src/Hook.sol:239: Reentrancy in Hook._collect(uint256,address,bool,uint256) (src/Hook.sol#239-257):\n[low/medium] reentrancy-events at src/Hook.sol:265: Reentrancy in Hook.unlockCallback(bytes) (src/Hook.sol#265-273):\n[low/medium] timestamp at src/PleaRegistry.sol:82: PleaRegistry.sleeping() (src/PleaRegistry.sol#82-84) uses timestamp for comparisons\n[low/medium] timestamp at src/BurnVault.sol:44: BurnVault.buyback(uint256) (src/BurnVault.sol#44-62) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:142: StakingVault.claim() (src/StakingVault.sol#142-153) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:157: PleaRegistry._answer(bytes32,OracleAttestation.Attestation,bytes) (src/PleaRegistry.sol#157-190) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:196: StakingVault.streamRate() (src/StakingVault.sol#196-200) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:232: StakingVault._settle(address) (src/StakingVault.sol#232-243) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:125: PleaRegistry.questionPrefix(uint256) (src/PleaRegistry.sol#125-131) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:123: StakingVault.unstake(uint256) (src/StakingVault.sol#123-140) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:275: Hook._observe() (src/Hook.sol#275-283) uses timestamp for comparisons\n[low/medium] timestamp at src/PrayRouter.sol:40: PrayRouter.swap(PrayRouter.Trade) (src/PrayRouter.sol#40-57) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:213: StakingVault._released(uint256) (src/StakingVault.sol#213-217) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:91: PleaRegistry.filePlea(uint256,string) (src/PleaRegistry.sol#91-123) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:219: StakingVault._update() (src/StakingVault.sol#219-228) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:254: StakingVault._accrued(uint256,uint256,uint256) (src/StakingVault.sol#254-257) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:86: PleaRegistry.available(address) (src/PleaRegistry.sol#86-89) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:155: StakingVault.unlockCallback(bytes) (src/StakingVault.sol#155-164) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:192: PleaRegistry.expire(uint256) (src/PleaRegistry.sol#192-200) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:285: Hook.buybackFloor(uint256) (src/Hook.sol#285-311) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:164: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#164-175) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:133: Hook.buyFeeBps() (src/Hook.sol#133-137) uses timestamp for comparisons","passed":true},{"durationMs":912,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/PleaText.sol:107: `abi.encodePacked()` Hash Collision (3 places)\n[high] reentrancy-state-change at src/BurnVault.sol:49: Reentrancy: State change after external call (10 places)\n[high] unsafe-casting at src/Hook.sol:297: Unsafe Casting of integers\n[high] yul-return at src/PleaText.sol:144: Yul block contains `return`\n[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once (3 places)\n[low] large-numeric-literal at src/BurnVault.sol:20: Large Numeric Literal (9 places)\n[low] literal-instead-of-constant at src/BurnVault.sol:46: Literal Instead of Constant (44 places)\n[low] local-variable-shadowing at src/Hook.sol:53: Local Variable Shadows State Variable\n[low] require-revert-in-loop at src/PleaText.sol:13: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/BurnVault.sol:64: State Change Without Event (4 places)\n[low] unchecked-return at src/StakingVault.sol:151: Unchecked Return\n[low] unused-public-function at src/Hook.sol:99: Public Function Not Used Internally (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2b096d410f3e3a12626d86a62a97e6cbc99bb69a289c9a4bfab0a9facd44e302","verifiedTreeHash":"a9894c2c8cf99476027fa4172e67f3c820b31936","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":50627,"exitCode":0,"name":"build","output":"Compiling 108 files with Solc 0.8.26\nSolc 0.8.26 finished in 50.38s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/Registry.t.sol:224:5:\n    |\n224 |     function testFuzzJsonEscapeRoundTrip(bytes calldata raw) public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/PrayRouter.sol:64:16\n   │\n64 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/BurnVault.sol:68:16\n   │\n68 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PrayRouter.sol:42:13\n   │\n42 │             block.timestamp > trade.deadline || trade.maxInput == 0\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/PrayRouter.sol:56:9\n   │\n56 │         emit Traded(msg.sender, trade.buy, paid, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:43:37\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 0\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/PrayRouter.sol:43:45\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 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/BurnVault.sol:45:50\n   │\n45 │         if (budget == 0 || budget > MAX_SPEND || block.timestamp < lastBuyback + 60) revert InvalidBuyback();\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/BurnVault.sol:61:9\n   │\n61 │         emit BoughtAndBurned(msg.sender, spend, reward, burned);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaRegistry.sol:29:38\n   │\n29 │     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[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:74:24\n   │\n74 │         uint256 paid = uint256(-int256(inputDelta));\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/PrayRouter.sol:75:28\n   │\n75 │         uint256 received = uint256(int256(outputDelta));\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/PrayRouter.sol:78:58\n   │\n78 │                 || (t.amountSpecified > 0 && received != uint256(t.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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PleaRegistry.sol:83:16\n   │\n83 │         return block.timestamp >= lastAnswer + 24 hours;\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/PleaRegistry.sol:88:16\n   │\n88 │         return block.timestamp < b.until ? b.remaining : 0;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:77:18\n   │\n77 │                 -int256(spend),\n   │                  ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:23\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\n   │                       ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:55\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\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/BurnVault.sol:89:26\n   │\n89 │         uint256 burned = uint256(int256(output));\n   │                          ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PleaRegistry.sol:96:43\n   │\n96 │                 || (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours)\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/PleaRegistry.sol:122:9\n    │\n122 │         emit PleaFiled(id, msg.sender, requestId, amount, text);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:103:13\n    │\n103 │             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[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:31:17\n   │\n31 │                 revert InvalidText();\n   │                 ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:33:36\n   │\n33 │             if (i + n >= s.length) revert InvalidText();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:36:43\n   │\n36 │                 if (d < 0x80 || d > 0xbf) revert InvalidText();\n   │                                           ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:42:15\n   │\n42 │             ) revert InvalidText();\n   │               ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:62:27\n   │\n62 │                         ? bytes1(\"b\")\n   │                           ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:36\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:60\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                            ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:84\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:98\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                                  ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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-events-access-control]: `active` is changed without an event but is used for access control\n    ╭▸ src/StakingVault.sol:125:16\n    │\n125 │         delete active;\n    │                ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:107:16\n    │\n107 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n108 │ ┃             '{\"answerType\":\"bool\",\"chainId\":1,\"definitions\":{\"rubric\":\"',\n109 │ ┃             RUBRIC,\n110 │ ┃             '\"},\"evidence\":\"panel\",\"question\":\"',\n111 │ ┃             escape(q),\n112 │ ┃             '\",\"v\":1,\"window\":'\n113 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:57:55\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\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/StakingVault.sol:57:69\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\n   │                                                                     ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:117:16\n    │\n117 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n118 │ ┃             p,\n119 │ ┃             '{\"hours\":1},\"panelSize\":7,\"quorum\":5,\"validForSeconds\":900,\"consumer\":{\"chainId\":1,\"verifyingContract\":\"',\n120 │ ┃             Strings.toHexString(registry),\n121 │ ┃             '\"}}'\n122 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:127:13\n    │\n127 │ ┏             abi.encodePacked(\n128 │ ┃                 p,\n129 │ ┃                 '{\"fromBlock\":',\n130 │ ┃                 Strings.toString(fromBlock),\n    ‡ ┃\n133 │ ┃                 \"}}\"\n134 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/StakingVault.sol:73:13\n   │\n73 │             emit RewardReceived(amount, true);\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/StakingVault.sol:76:13\n   │\n76 │             emit RewardReceived(amount, false);\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/StakingVault.sol:197:9\n    │\n197 │         emit StreamStarted(streamRemaining, streamEnd);\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/StakingVault.sol:89:9\n   │\n89 │         emit Staked(msg.sender, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:106:9\n    │\n106 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:118:9\n    │\n118 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/StakingVault.sol:117:9\n    │\n117 │         manager.unlock(data);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:147:13\n    │\n147 │         if (streamEnd <= block.timestamp || totalStaked == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:159:57\n    │\n159 │         if (streamRemaining == 0 || totalStaked == 0 || block.timestamp <= lastUpdate) return 0;\n    │                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:160:13\n    │\n160 │         if (block.timestamp >= streamEnd) return streamRemaining;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:166:13\n    │\n166 │         if (released > 0) {\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/PleaRegistry.sol:162:43\n    │\n162 │         if (p.status != Status.Pending || block.timestamp >= uint256(p.filedAt) + PENDING_LIFETIME) {\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/PleaRegistry.sol:180:39\n    │\n180 │             if (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours) {\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/PleaRegistry.sol:187:9\n    │\n187 │ ┏         emit PleaAnswered(\n188 │ ┃             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\n189 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:184:23\n    │\n184 │             b.until = uint64(block.timestamp + 15 minutes);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:185:29\n    │\n185 │             b.lastGranted = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:188:50\n    │\n188 │             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\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/PleaRegistry.sol:194:43\n    │\n194 │         if (p.status != Status.Pending || block.timestamp < uint256(p.filedAt) + PENDING_LIFETIME) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-events-arithmetic]: `cumulative` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:266:9\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observedAt` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:267:9\n    │\n267 │         observedAt = uint64(block.timestamp);\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:223:9\n    │\n223 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:129:9\n    │\n129 │         emit PoolOpened(PoolId.unwrap(poolId), address(registry), address(router), address(vault));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:126:22\n    │\n126 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:128:39\n    │\n128 │         observations[0] = Observation(uint64(block.timestamp), 0);\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/Hook.sol:136:13\n    │\n136 │             elapsed >= DECAY ? LOW_FEE_BPS : LAUNCH_FEE_BPS - (LAUNCH_FEE_BPS - LOW_FEE_BPS) * elapsed / DECAY;\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/Hook.sol:238:9\n    │\n238 │         emit FeeCollected(seller, buy, fee, vaultPart, treasuryPart, rate);\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/Hook.sol:243:13\n    │\n243 │             emit SinnersTax(seller, fee, vaultPart, treasuryPart);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:167:66\n    │\n167 │             blessed = available > 0 && (p.amountSpecified > 0 || uint256(-p.amountSpecified) <= available);\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/Hook.sol:179:54\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:179:84\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:183:23\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:183:31\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:189:63\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/Hook.sol:189:70\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:204:34\n    │\n204 │         address seller = address(uint160(_load(SELLER)));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/Hook.sol:206:67\n    │\n206 │         if (!buy && _load(BLESSED) != 0) registry.consume(seller, uint256(-int256(prayDelta)));\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/Hook.sol:211:51\n    │\n211 │             int256 expected = p.amountSpecified + int256(_load(UPFRONT));\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:214:36\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:214:65\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:218:23\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:218:31\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:225:44\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(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/Hook.sol:225:51\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                   ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:260:9\n    │\n260 │         emit TreasuryPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Hook.sol:268:13\n    │\n268 │         if (block.timestamp >= uint256(observations[observationIndex].time) + 60) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:266:23\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\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/Hook.sol:266:49\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │                                                 ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:267:22\n    │\n267 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:270:58\n    │\n270 │             observations[observationIndex] = Observation(uint64(block.timestamp), cumulative);\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/Hook.sol:275:29\n    │\n275 │         if (!initialized || block.timestamp < launchedAt + 30 minutes) revert HistoryTooShort();\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/Hook.sol:279:38\n    │\n279 │             if (o.time > old.time && uint256(o.time) + 30 minutes <= block.timestamp) old = o;\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:282:66\n    │\n282 │         int256 current = int256(cumulative) + int256(lastTick) * int256(block.timestamp - observedAt);\n    │                                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:37\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:45\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\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/Hook.sol:285:93\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                                                                             ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:286:52\n    │\n286 │         uint160 sqrt = TickMath.getSqrtPriceAtTick(int24(average));\n    │                                                    ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:86:9\n    │\n 86 │         vm.warp(hook.launchedAt() + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    ‡\n250 │         vm.warp(hook.launchedAt() + 1800);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/Hook.t.sol:174:17\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:178:9\n    │\n178 │         vm.warp(registry.lastAnswer() + 86400);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/Registry.t.sol:94:17\n   │\n94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:178:17\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:238:17\n    │\n238 │         vm.warp(block.timestamp + 86400);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Staking.t.sol:347:9\n    │\n347 │         vm.warp(vm.getBlockTimestamp() + 1 days);\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":321,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testProtocolDigestSignatureAndSelector() (gas: 713241)\n[PASS] testRealMainnetSignerAttestation() (gas: 3350374)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.11ms (1.33ms CPU time)\n\nRan 15 tests for test/Registry.t.sol:RegistryTest\n[PASS] testCallbackUnknownRequestAndUnauthorized() (gas: 1498533)\n[PASS] testCallbackWithinStipendAndPublicFallback() (gas: 2551614)\n[PASS] testEscapedTextIsValidJson() (gas: 1397628)\n[PASS] testExpirePendingNoRefundAndWakeOnNextAnswer() (gas: 2876156)\n[PASS] testExpiredAndFutureAttestations() (gas: 1516753)\n[PASS] testFalseAnswerStoresVerdictAndDoesNotBless() (gas: 1540611)\n[PASS] testFilePleaPaysAndPinsTemplate() (gas: 1506099)\n[PASS] testFuzzJsonEscapeRoundTrip(bytes) (runs: 128, μ: 47729, ~: 53873)\n[PASS] testMaxEscapedTextCallbackUnder200k() (gas: 5787979)\n[PASS] testOneOpenAndOneBlessingPer24Hours() (gas: 2535644)\n[PASS] testOnlyHookCanConsume() (gas: 1681716)\n[PASS] testPleaSizeLimitBalanceAndUtf8() (gas: 1567901)\n[PASS] testReplayAndWrongPleaCannotBorrowApproval() (gas: 2534426)\n[PASS] testWrongSignerAndDomain() (gas: 1603646)\n[PASS] testWrongTypeChainPanelAndWindow() (gas: 1807997)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 207.04ms (39.84ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:ReverseOrderingHookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980258)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3861352, ~: 3864011)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378599, ~: 378711)\nLogs:\n  Bound result 1000003\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985389)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905851)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 207.25ms (228.80ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:HookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980246)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3860824, ~: 3856325)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378596, ~: 378711)\nLogs:\n  Bound result 9999999999999999025532\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985353)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905815)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 207.38ms (247.99ms CPU time)\n\nRan 25 tests for test/Staking.t.sol:StakingTest\n[PASS] testAccrualAndClaimPaysFwaFromManager() (gas: 867062)\n[PASS] testBlessedSellGoesToBurnVault() (gas: 1955232)\n[PASS] testBuybackWithoutArgumentUsesBalanceCapped() (gas: 857463)\n[PASS] testDustStakerCannotOverdraw() (gas: 1285684)\n[PASS] testEveryoneLeavesMidStreamRemainderHeldAgain() (gas: 1091040)\n[PASS] testFlashStakeAroundOwnSellCannotUnstakeSameBlock() (gas: 817456)\n[PASS] testFuzzSinnerSplitConservation(uint96) (runs: 128, μ: 463730, ~: 463958)\nLogs:\n  Bound result 1000000003\n\n[PASS] testFuzzVaultSolvency(uint256) (runs: 128, μ: 5175882, ~: 5237986)\n[PASS] testLaunchBuyAtFiftyPercentGoesToBurnVaultNotStakers() (gas: 338789)\n[PASS] testLaunchDecayExactPoints() (gas: 68467)\n[PASS] testNoRetroactiveRewards() (gas: 1042611)\n[PASS] testOnlyHookCanNotify() (gas: 55664)\n[PASS] testProportionalAccrual() (gas: 765073)\n[PASS] testRatesAreConstants() (gas: 37770)\n[PASS] testSleepingSellGoesToBurnVault() (gas: 377653)\n[PASS] testStakeAndUnstakeNoLock() (gas: 404056)\n[PASS] testStakeUnstakeValidation() (gas: 356036)\n[PASS] testStreamSharedByLateStakerOnlyFromJoining() (gas: 863546)\n[PASS] testTaxDuringStreamPaidImmediatelyAndStreamContinues() (gas: 944961)\n[PASS] testTaxWithNoStakersIsHeldThenStreamedOverSevenDays() (gas: 1055614)\n[PASS] testTopUpSettlesFirst() (gas: 1130466)\n[PASS] testUnblessedSellExactInputSplitToStakers() (gas: 469531)\n[PASS] testUnblessedSellExactOutputSplitToStakers() (gas: 436453)\n[PASS] testUnlockCallbackRefusesOutsiders() (gas: 66679)\n[PASS] testUnstakeKeepsEarnedRewardsAndStopsAccrual() (gas: 1241224)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 207.37ms (187.91ms CPU time)\n\nRan 5 test suites in 209.02ms (830.14ms CPU time): 98 tests passed, 0 failed, 0 skipped (98 total tests)\n","passed":true},{"durationMs":88,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BurnVault.buyback()\",\"BurnVault.buyback(uint256)\",\"BurnVault.unlockCallback(bytes)\",\"Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Hook.redeemTreasury()\",\"Hook.unlockCallback(bytes)\",\"PleaRegistry.consume(address,uint256)\",\"PleaRegistry.expire(uint256)\",\"PleaRegistry.filePlea(uint256,string)\",\"PleaRegistry.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PleaRegistry.submit(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PrayRouter.swap((bool,int256,uint256,uint256,uint160,uint256))\",\"PrayRouter.unlockCallback(bytes)\",\"StakingVault.claim()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.unlockCallback(bytes)\",\"StakingVault.unstake(uint256)\",\"Token.approve(address,uint256)\",\"Token.burn(uint256)\",\"Token.transfer(address,uint256)\",\"Token.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":142,\"deployment-parameters.json\":40,\"docs/REVIEW.md\":75,\"docs/VALIDATION.md\":54,\"fork/Mainnet.t.sol\":72,\"foundry.toml\":19,\"launch.json\":31,\"remappings.txt\":4,\"scripts/check-build.mjs\":15,\"scripts/check-oracle.mjs\":11,\"scripts/configure-site.mjs\":18,\"scripts/mine-hook.mjs\":9,\"site/app.js\":66,\"site/deployment.json\":7,\"site/index.html\":24,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/BurnVault.sol\":94,\"src/Hook.sol\":301,\"src/HookFlags.sol\":30,\"src/IPrayPool.sol\":10,\"src/OracleAttestation.sol\":239,\"src/PleaRegistry.sol\":208,\"src/PleaText.sol\":146,\"src/PrayRouter.sol\":84,\"src/StakingVault.sol\":199,\"src/Token.sol\":14,\"test/Hook.t.sol\":376,\"test/OracleConformance.t.sol\":100,\"test/Registry.t.sol\":271,\"test/Staking.t.sol\":444,\"test/TestBase.sol\":252,\"test/fixtures/live-attestation.json\":1,\"test/fixtures/live-request.json\":1,\"test/mocks/MockERC20.sol\":29,\"test/mocks/MockIntake.sol\":36,\"test/site/quote-race.mjs\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"47423231212485ed66a3e86fe14477e2e8209c34dd4402596e8200791a914268","verifiedTreeHash":"c2bae2d84c5b5377ecdac9b46e93400df9132ad7","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":46724,"exitCode":0,"name":"build","output":"Compiling 108 files with Solc 0.8.26\nSolc 0.8.26 finished in 46.52s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/Registry.t.sol:224:5:\n    |\n224 |     function testFuzzJsonEscapeRoundTrip(bytes calldata raw) public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/PrayRouter.sol:64:16\n   │\n64 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:31:17\n   │\n31 │                 revert InvalidText();\n   │                 ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:33:36\n   │\n33 │             if (i + n >= s.length) revert InvalidText();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:36:43\n   │\n36 │                 if (d < 0x80 || d > 0xbf) revert InvalidText();\n   │                                           ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/PleaText.sol:42:15\n   │\n42 │             ) revert InvalidText();\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/PrayRouter.sol:42:13\n   │\n42 │             block.timestamp > trade.deadline || trade.maxInput == 0\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/PrayRouter.sol:56:9\n   │\n56 │         emit Traded(msg.sender, trade.buy, paid, received);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaRegistry.sol:29:38\n   │\n29 │     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[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PrayRouter.sol:43:37\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 0\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/PrayRouter.sol:43:45\n   │\n43 │                 || trade.maxInput > uint256(uint128(type(int128).max)) || trade.minOutput == 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[missing-events-access-control]: `active` is changed without an event but is used for access control\n   ╭▸ src/BurnVault.sol:68:16\n   │\n68 │         delete active;\n   │                ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:62:27\n   │\n62 │                         ? bytes1(\"b\")\n   │                           ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaRegistry.sol:83:16\n   │\n83 │         return block.timestamp >= lastAnswer + 24 hours;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:63:36\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:60\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                            ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaText.sol:63:84\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                    ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PleaRegistry.sol:88:16\n   │\n88 │         return block.timestamp < b.until ? b.remaining : 0;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/PleaText.sol:63:98\n   │\n63 │                         : c == 9 ? bytes1(\"t\") : c == 10 ? bytes1(\"n\") : c == 12 ? bytes1(\"f\") : bytes1(\"r\");\n   │                                                                                                  ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes1' is safe 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/PrayRouter.sol:74:24\n   │\n74 │         uint256 paid = uint256(-int256(inputDelta));\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/PrayRouter.sol:75:28\n   │\n75 │         uint256 received = uint256(int256(outputDelta));\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/PrayRouter.sol:78:58\n   │\n78 │                 || (t.amountSpecified > 0 && received != uint256(t.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[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:107:16\n    │\n107 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n108 │ ┃             '{\"answerType\":\"bool\",\"chainId\":1,\"definitions\":{\"rubric\":\"',\n109 │ ┃             RUBRIC,\n110 │ ┃             '\"},\"evidence\":\"panel\",\"question\":\"',\n111 │ ┃             escape(q),\n112 │ ┃             '\",\"v\":1,\"window\":'\n113 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:117:16\n    │\n117 │           return abi.encodePacked(\n    │ ┏━━━━━━━━━━━━━━━━┛\n118 │ ┃             p,\n119 │ ┃             '{\"hours\":1},\"panelSize\":7,\"quorum\":5,\"validForSeconds\":900,\"consumer\":{\"chainId\":1,\"verifyingContract\":\"',\n120 │ ┃             Strings.toHexString(registry),\n121 │ ┃             '\"}}'\n122 │ ┃         );\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n    ╭▸ src/PleaText.sol:127:13\n    │\n127 │ ┏             abi.encodePacked(\n128 │ ┃                 p,\n129 │ ┃                 '{\"fromBlock\":',\n130 │ ┃                 Strings.toString(fromBlock),\n    ‡ ┃\n133 │ ┃                 \"}}\"\n134 │ ┃             )\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PleaRegistry.sol:96:43\n   │\n96 │                 || (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours)\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/PleaRegistry.sol:122:9\n    │\n122 │         emit PleaFiled(id, msg.sender, requestId, amount, text);\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/BurnVault.sol:45:50\n   │\n45 │         if (budget == 0 || budget > MAX_SPEND || block.timestamp < lastBuyback + 60) revert InvalidBuyback();\n   │                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:103:13\n    │\n103 │             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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/BurnVault.sol:61:9\n   │\n61 │         emit BoughtAndBurned(msg.sender, spend, reward, burned);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `active` is changed without an event but is used for access control\n    ╭▸ src/StakingVault.sol:125:16\n    │\n125 │         delete active;\n    │                ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/StakingVault.sol:57:69\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\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/StakingVault.sol:57:55\n   │\n57 │     constructor(IPoolManager manager_, IERC20 token_, address fwa_, address hook_) {\n   │                                                       ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:77:18\n   │\n77 │                 -int256(spend),\n   │                  ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:23\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\n   │                       ━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/BurnVault.sol:84:55\n   │\n84 │         if (input != -int256(spend) || output <= 0 || uint256(int256(output)) < floor) {\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/BurnVault.sol:89:26\n   │\n89 │         uint256 burned = uint256(int256(output));\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/StakingVault.sol:73:13\n   │\n73 │             emit RewardReceived(amount, true);\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/StakingVault.sol:76:13\n   │\n76 │             emit RewardReceived(amount, false);\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/StakingVault.sol:197:9\n    │\n197 │         emit StreamStarted(streamRemaining, streamEnd);\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/StakingVault.sol:89:9\n   │\n89 │         emit Staked(msg.sender, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:106:9\n    │\n106 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/StakingVault.sol:118:9\n    │\n118 │         emit Claimed(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/StakingVault.sol:117:9\n    │\n117 │         manager.unlock(data);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:147:13\n    │\n147 │         if (streamEnd <= block.timestamp || totalStaked == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:159:57\n    │\n159 │         if (streamRemaining == 0 || totalStaked == 0 || block.timestamp <= lastUpdate) return 0;\n    │                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:160:13\n    │\n160 │         if (block.timestamp >= streamEnd) return streamRemaining;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/StakingVault.sol:166:13\n    │\n166 │         if (released > 0) {\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/PleaRegistry.sol:162:43\n    │\n162 │         if (p.status != Status.Pending || block.timestamp >= uint256(p.filedAt) + PENDING_LIFETIME) {\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/PleaRegistry.sol:180:39\n    │\n180 │             if (b.lastGranted != 0 && block.timestamp < uint256(b.lastGranted) + 24 hours) {\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/PleaRegistry.sol:187:9\n    │\n187 │ ┏         emit PleaAnswered(\n188 │ ┃             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\n189 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:184:23\n    │\n184 │             b.until = uint64(block.timestamp + 15 minutes);\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:185:29\n    │\n185 │             b.lastGranted = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PleaRegistry.sol:188:50\n    │\n188 │             id, a.requestId, approved, a.agreed, uint64(block.timestamp + (approved ? 15 minutes : 0))\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/PleaRegistry.sol:194:43\n    │\n194 │         if (p.status != Status.Pending || block.timestamp < uint256(p.filedAt) + PENDING_LIFETIME) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-events-arithmetic]: `cumulative` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:266:9\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observedAt` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:267:9\n    │\n267 │         observedAt = uint64(block.timestamp);\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/Hook.sol:223:9\n    │\n223 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:129:9\n    │\n129 │         emit PoolOpened(PoolId.unwrap(poolId), address(registry), address(router), address(vault));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:126:22\n    │\n126 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:128:39\n    │\n128 │         observations[0] = Observation(uint64(block.timestamp), 0);\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/Hook.sol:136:13\n    │\n136 │             elapsed >= DECAY ? LOW_FEE_BPS : LAUNCH_FEE_BPS - (LAUNCH_FEE_BPS - LOW_FEE_BPS) * elapsed / DECAY;\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/Hook.sol:238:9\n    │\n238 │         emit FeeCollected(seller, buy, fee, vaultPart, treasuryPart, rate);\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/Hook.sol:243:13\n    │\n243 │             emit SinnersTax(seller, fee, vaultPart, treasuryPart);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:167:66\n    │\n167 │             blessed = available > 0 && (p.amountSpecified > 0 || uint256(-p.amountSpecified) <= available);\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/Hook.sol:179:54\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:179:84\n    │\n179 │             uint256 amount = p.amountSpecified < 0 ? uint256(-p.amountSpecified) : uint256(p.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/Hook.sol:183:23\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:183:31\n    │\n183 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:189:63\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(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/Hook.sol:189:70\n    │\n189 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:204:34\n    │\n204 │         address seller = address(uint160(_load(SELLER)));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/Hook.sol:206:67\n    │\n206 │         if (!buy && _load(BLESSED) != 0) registry.consume(seller, uint256(-int256(prayDelta)));\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/Hook.sol:211:51\n    │\n211 │             int256 expected = p.amountSpecified + int256(_load(UPFRONT));\n    │                                                   ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:214:36\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:214:65\n    │\n214 │             uint256 amount = buy ? uint256(-int256(fwaDelta)) : uint256(int256(fwaDelta));\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/Hook.sol:218:23\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:218:31\n    │\n218 │             if (fee > uint256(uint128(type(int128).max))) revert InvalidSwap();\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/Hook.sol:225:44\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(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/Hook.sol:225:51\n    │\n225 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                   ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Hook.sol:260:9\n    │\n260 │         emit TreasuryPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Hook.sol:268:13\n    │\n268 │         if (block.timestamp >= uint256(observations[observationIndex].time) + 60) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:266:23\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\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/Hook.sol:266:49\n    │\n266 │         cumulative += int128(int256(lastTick) * int256(elapsed));\n    │                                                 ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:267:22\n    │\n267 │         observedAt = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:270:58\n    │\n270 │             observations[observationIndex] = Observation(uint64(block.timestamp), cumulative);\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/Hook.sol:275:29\n    │\n275 │         if (!initialized || block.timestamp < launchedAt + 30 minutes) revert HistoryTooShort();\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/Hook.sol:279:38\n    │\n279 │             if (o.time > old.time && uint256(o.time) + 30 minutes <= block.timestamp) old = o;\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:282:66\n    │\n282 │         int256 current = int256(cumulative) + int256(lastTick) * int256(block.timestamp - observedAt);\n    │                                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:37\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:285:45\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\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/Hook.sol:285:93\n    │\n285 │         int256 average = sum < 0 ? -int256((uint256(-sum) + elapsed - 1) / elapsed) : sum / int256(elapsed);\n    │                                                                                             ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Hook.sol:286:52\n    │\n286 │         uint160 sqrt = TickMath.getSqrtPriceAtTick(int24(average));\n    │                                                    ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:86:9\n    │\n 86 │         vm.warp(hook.launchedAt() + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    ‡\n250 │         vm.warp(hook.launchedAt() + 1800);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:174:9\n    │\n174 │         vm.warp(block.timestamp + 900);\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/Hook.t.sol:174:17\n    │\n174 │         vm.warp(block.timestamp + 900);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Hook.t.sol:178:9\n    │\n178 │         vm.warp(registry.lastAnswer() + 86400);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:94:9\n    │\n 94 │         vm.warp(block.timestamp + 901);\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/Registry.t.sol:94:17\n   │\n94 │         vm.warp(block.timestamp + 901);\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/TestBase.sol:233:20\n    │\n233 │             uint64(block.timestamp),\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/TestBase.sol:234:20\n    │\n234 │             uint64(block.timestamp + 900)\n    │                    ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Registry.t.sol:178:9\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:178:17\n    │\n178 │         vm.warp(block.timestamp + 86399);\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/Registry.t.sol:238:17\n    │\n238 │         vm.warp(block.timestamp + 86400);\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/TestBase.sol:205:78\n    │\n205 │         return router.swap(PrayRouter.Trade(buy, amount, max, 1, limit(buy), block.timestamp));\n    │                                                                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/Staking.t.sol:347:9\n    │\n347 │         vm.warp(vm.getBlockTimestamp() + 1 days);\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":348,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testProtocolDigestSignatureAndSelector() (gas: 713241)\n[PASS] testRealMainnetSignerAttestation() (gas: 3350374)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.33ms (2.94ms CPU time)\n\nRan 15 tests for test/Registry.t.sol:RegistryTest\n[PASS] testCallbackUnknownRequestAndUnauthorized() (gas: 1498533)\n[PASS] testCallbackWithinStipendAndPublicFallback() (gas: 2551614)\n[PASS] testEscapedTextIsValidJson() (gas: 1397628)\n[PASS] testExpirePendingNoRefundAndWakeOnNextAnswer() (gas: 2876156)\n[PASS] testExpiredAndFutureAttestations() (gas: 1516753)\n[PASS] testFalseAnswerStoresVerdictAndDoesNotBless() (gas: 1540611)\n[PASS] testFilePleaPaysAndPinsTemplate() (gas: 1506099)\n[PASS] testFuzzJsonEscapeRoundTrip(bytes) (runs: 128, μ: 48169, ~: 54074)\n[PASS] testMaxEscapedTextCallbackUnder200k() (gas: 5787979)\n[PASS] testOneOpenAndOneBlessingPer24Hours() (gas: 2535644)\n[PASS] testOnlyHookCanConsume() (gas: 1681716)\n[PASS] testPleaSizeLimitBalanceAndUtf8() (gas: 1567901)\n[PASS] testReplayAndWrongPleaCannotBorrowApproval() (gas: 2534426)\n[PASS] testWrongSignerAndDomain() (gas: 1603646)\n[PASS] testWrongTypeChainPanelAndWindow() (gas: 1807997)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 19.79ms (44.60ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:HookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980246)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3861339, ~: 3856069)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378609, ~: 378711)\nLogs:\n  Bound result 9999999999999999187234\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985353)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905815)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 207.69ms (238.82ms CPU time)\n\nRan 25 tests for test/Staking.t.sol:StakingTest\n[PASS] testAccrualAndClaimPaysFwaFromManager() (gas: 867062)\n[PASS] testBlessedSellGoesToBurnVault() (gas: 1955232)\n[PASS] testBuybackWithoutArgumentUsesBalanceCapped() (gas: 857463)\n[PASS] testDustStakerCannotOverdraw() (gas: 1285684)\n[PASS] testEveryoneLeavesMidStreamRemainderHeldAgain() (gas: 1091040)\n[PASS] testFlashStakeAroundOwnSellCannotUnstakeSameBlock() (gas: 817456)\n[PASS] testFuzzSinnerSplitConservation(uint96) (runs: 128, μ: 463791, ~: 463958)\nLogs:\n  Bound result 99999999999999000020728\n\n[PASS] testFuzzVaultSolvency(uint256) (runs: 128, μ: 5152641, ~: 5213326)\n[PASS] testLaunchBuyAtFiftyPercentGoesToBurnVaultNotStakers() (gas: 338789)\n[PASS] testLaunchDecayExactPoints() (gas: 68467)\n[PASS] testNoRetroactiveRewards() (gas: 1042611)\n[PASS] testOnlyHookCanNotify() (gas: 55664)\n[PASS] testProportionalAccrual() (gas: 765073)\n[PASS] testRatesAreConstants() (gas: 37770)\n[PASS] testSleepingSellGoesToBurnVault() (gas: 377653)\n[PASS] testStakeAndUnstakeNoLock() (gas: 404056)\n[PASS] testStakeUnstakeValidation() (gas: 356036)\n[PASS] testStreamSharedByLateStakerOnlyFromJoining() (gas: 863546)\n[PASS] testTaxDuringStreamPaidImmediatelyAndStreamContinues() (gas: 944961)\n[PASS] testTaxWithNoStakersIsHeldThenStreamedOverSevenDays() (gas: 1055614)\n[PASS] testTopUpSettlesFirst() (gas: 1130466)\n[PASS] testUnblessedSellExactInputSplitToStakers() (gas: 469531)\n[PASS] testUnblessedSellExactOutputSplitToStakers() (gas: 436453)\n[PASS] testUnlockCallbackRefusesOutsiders() (gas: 66679)\n[PASS] testUnstakeKeepsEarnedRewardsAndStopsAccrual() (gas: 1241224)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 224.40ms (201.48ms CPU time)\n\nRan 28 tests for test/Hook.t.sol:ReverseOrderingHookTest\n[PASS] testBlessedExactOutput() (gas: 1931379)\n[PASS] testBlessedSellConsumesOnlyActualAmount() (gas: 2460985)\n[PASS] testBlessingWrongSellerDoesNotDiscount() (gas: 1986369)\n[PASS] testBuyDecayAndFreshManagerHasNoETH() (gas: 819290)\n[PASS] testBuyExactOutputAndRefund() (gas: 375540)\n[PASS] testBuybackBurnsAndPaysFromManager() (gas: 1246796)\n[PASS] testBuybackCapsHistoryAndBalance() (gas: 462468)\n[PASS] testBuybackMaximumBudgetIncludesReward() (gas: 820913)\n[PASS] testBuybackResistsSameBlockSpotManipulation() (gas: 1109948)\n[PASS] testDustBuybackCannotResetCooldown() (gas: 964119)\n[PASS] testExpiredBlessingAndSleeping() (gas: 2286977)\n[PASS] testForeignRouterCannotSpoofHookData() (gas: 1980258)\n[PASS] testFuzzSequenceConservesClaimShares(uint256) (runs: 128, μ: 3860430, ~: 3855867)\n[PASS] testFuzzSplitConservation(uint96) (runs: 128, μ: 378610, ~: 378711)\nLogs:\n  Bound result 9999999999999999020909\n\n[PASS] testFwaWalletToRouterReallyReverts() (gas: 33214)\n[PASS] testInitializationAndFlags() (gas: 16954)\n[PASS] testOneSidedPoolNoFwaCashBeforeSwap() (gas: 705570)\n[PASS] testOverAmountExactInputPaysHighAndKeepsBlessing() (gas: 1985389)\n[PASS] testOverAmountExactOutputRevertsAtomically() (gas: 1905851)\n[PASS] testPartialBuyRevertsNoUpfrontTaxLost() (gas: 327425)\n[PASS] testRejectOtherPoolAndReinitialize() (gas: 78691)\n[PASS] testRouterCannotPullVictimAllowance() (gas: 397459)\n[PASS] testRouterSlippageDeadlineAndFailedRefundAtomicity() (gas: 401851)\n[PASS] testSplitAndTreasuryOnlyFixedRecipient() (gas: 541024)\n[PASS] testSupplyTransferAndBurn() (gas: 134096)\n[PASS] testUnauthorizedCallbacks() (gas: 181226)\n[PASS] testUnblessedSellExactInput() (gas: 417445)\n[PASS] testUnblessedSellExactOutput() (gas: 420294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 224.78ms (250.53ms CPU time)\n\nRan 5 test suites in 226.84ms (678.99ms CPU time): 98 tests passed, 0 failed, 0 skipped (98 total tests)\n","passed":true},{"durationMs":65,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BurnVault.buyback()\",\"BurnVault.buyback(uint256)\",\"BurnVault.unlockCallback(bytes)\",\"Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Hook.redeemTreasury()\",\"Hook.unlockCallback(bytes)\",\"PleaRegistry.consume(address,uint256)\",\"PleaRegistry.expire(uint256)\",\"PleaRegistry.filePlea(uint256,string)\",\"PleaRegistry.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PleaRegistry.submit(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PrayRouter.swap((bool,int256,uint256,uint256,uint160,uint256))\",\"PrayRouter.unlockCallback(bytes)\",\"StakingVault.claim()\",\"StakingVault.notifyReward(uint256)\",\"StakingVault.stake(uint256)\",\"StakingVault.unlockCallback(bytes)\",\"StakingVault.unstake(uint256)\",\"Token.approve(address,uint256)\",\"Token.burn(uint256)\",\"Token.transfer(address,uint256)\",\"Token.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":142,\"deployment-parameters.json\":40,\"docs/REVIEW.md\":75,\"docs/VALIDATION.md\":54,\"fork/Mainnet.t.sol\":72,\"foundry.toml\":19,\"remappings.txt\":4,\"scripts/check-build.mjs\":15,\"scripts/check-oracle.mjs\":11,\"scripts/configure-site.mjs\":18,\"scripts/mine-hook.mjs\":9,\"site/app.js\":66,\"site/deployment.json\":7,\"site/index.html\":24,\"site/style.css\":1,\"site/vendor/ethers.LICENSE.md\":21,\"site/vendor/ethers.js\":1,\"src/BurnVault.sol\":94,\"src/Hook.sol\":301,\"src/HookFlags.sol\":30,\"src/IPrayPool.sol\":10,\"src/OracleAttestation.sol\":239,\"src/PleaRegistry.sol\":208,\"src/PleaText.sol\":146,\"src/PrayRouter.sol\":84,\"src/StakingVault.sol\":199,\"src/Token.sol\":14,\"test/Hook.t.sol\":376,\"test/OracleConformance.t.sol\":100,\"test/Registry.t.sol\":271,\"test/Staking.t.sol\":444,\"test/TestBase.sol\":252,\"test/fixtures/live-attestation.json\":1,\"test/fixtures/live-request.json\":1,\"test/mocks/MockERC20.sol\":29,\"test/mocks/MockIntake.sol\":36,\"test/site/quote-race.mjs\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":10270,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/StakingVault.sol:92: StakingVault.unstake(uint256) (src/StakingVault.sol#92-107) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:121: StakingVault.unlockCallback(bytes) (src/StakingVault.sol#121-130) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:158: StakingVault._released() (src/StakingVault.sol#158-162) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaRegistry.sol:125: PleaRegistry.questionPrefix(uint256) (src/PleaRegistry.sol#125-131) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:164: StakingVault._update() (src/StakingVault.sol#164-172) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/StakingVault.sol:109: StakingVault.claim() (src/StakingVault.sol#109-119) uses a dangerous strict equality:\n[medium/medium] unused-return at src/Hook.sol:192: Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#192-226) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/Hook.sol#222)\n[medium/medium] unused-return at src/StakingVault.sol:109: StakingVault.claim() (src/StakingVault.sol#109-119) ignores return value by manager.unlock(data) (src/StakingVault.sol#117)\n[low/medium] missing-zero-check at src/StakingVault.sol:57: StakingVault.constructor(IPoolManager,IERC20,address,address).fwa_ (src/StakingVault.sol#57) lacks a zero-check on :\n[low/medium] missing-zero-check at src/StakingVault.sol:57: StakingVault.constructor(IPoolManager,IERC20,address,address).hook_ (src/StakingVault.sol#57) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/BurnVault.sol:44: Reentrancy in BurnVault.buyback(uint256) (src/BurnVault.sol#44-62):\n[low/medium] reentrancy-benign at src/PleaRegistry.sol:91: Reentrancy in PleaRegistry.filePlea(uint256,string) (src/PleaRegistry.sol#91-123):\n[low/medium] reentrancy-benign at src/Hook.sol:248: Reentrancy in Hook.redeemTreasury() (src/Hook.sol#248-252):\n[low/medium] reentrancy-benign at src/PrayRouter.sol:40: Reentrancy in PrayRouter.swap(PrayRouter.Trade) (src/PrayRouter.sol#40-57):\n[low/medium] reentrancy-benign at src/Hook.sol:192: Reentrancy in Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#192-226):\n[low/medium] reentrancy-events at src/Hook.sol:192: Reentrancy in Hook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Hook.sol#192-226):\n[low/medium] reentrancy-events at src/Hook.sol:228: Reentrancy in Hook._collect(uint256,address,bool,uint256) (src/Hook.sol#228-246):\n[low/medium] reentrancy-events at src/Hook.sol:254: Reentrancy in Hook.unlockCallback(bytes) (src/Hook.sol#254-262):\n[low/medium] timestamp at src/StakingVault.sol:164: StakingVault._update() (src/StakingVault.sol#164-172) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:82: PleaRegistry.sleeping() (src/PleaRegistry.sol#82-84) uses timestamp for comparisons\n[low/medium] timestamp at src/BurnVault.sol:44: BurnVault.buyback(uint256) (src/BurnVault.sol#44-62) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:157: PleaRegistry._answer(bytes32,OracleAttestation.Attestation,bytes) (src/PleaRegistry.sol#157-190) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:109: StakingVault.claim() (src/StakingVault.sol#109-119) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:264: Hook._observe() (src/Hook.sol#264-272) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:174: StakingVault._settle(address) (src/StakingVault.sol#174-179) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:125: PleaRegistry.questionPrefix(uint256) (src/PleaRegistry.sol#125-131) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:146: StakingVault.streamRate() (src/StakingVault.sol#146-149) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:121: StakingVault.unlockCallback(bytes) (src/StakingVault.sol#121-130) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:186: StakingVault._accrued(uint256,uint256,uint256) (src/StakingVault.sol#186-189) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:92: StakingVault.unstake(uint256) (src/StakingVault.sol#92-107) uses timestamp for comparisons\n[low/medium] timestamp at src/PrayRouter.sol:40: PrayRouter.swap(PrayRouter.Trade) (src/PrayRouter.sol#40-57) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:91: PleaRegistry.filePlea(uint256,string) (src/PleaRegistry.sol#91-123) uses timestamp for comparisons\n[low/medium] timestamp at src/StakingVault.sol:158: StakingVault._released() (src/StakingVault.sol#158-162) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:86: PleaRegistry.available(address) (src/PleaRegistry.sol#86-89) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:274: Hook.buybackFloor(uint256) (src/Hook.sol#274-300) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaRegistry.sol:192: PleaRegistry.expire(uint256) (src/PleaRegistry.sol#192-200) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:164: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#164-175) uses timestamp for comparisons\n[low/medium] timestamp at src/Hook.sol:133: Hook.buyFeeBps() (src/Hook.sol#133-137) uses timestamp for comparisons","passed":true},{"durationMs":927,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/PleaText.sol:107: `abi.encodePacked()` Hash Collision (3 places)\n[high] reentrancy-state-change at src/BurnVault.sol:49: Reentrancy: State change after external call (10 places)\n[high] unsafe-casting at src/Hook.sol:286: Unsafe Casting of integers\n[high] yul-return at src/PleaText.sol:144: Yul block contains `return`\n[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once (3 places)\n[low] large-numeric-literal at src/BurnVault.sol:20: Large Numeric Literal (9 places)\n[low] literal-instead-of-constant at src/BurnVault.sol:46: Literal Instead of Constant (44 places)\n[low] local-variable-shadowing at src/Hook.sol:53: Local Variable Shadows State Variable\n[low] require-revert-in-loop at src/PleaText.sol:13: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/BurnVault.sol:64: State Change Without Event (4 places)\n[low] unchecked-return at src/StakingVault.sol:117: Unchecked Return\n[low] unused-public-function at src/Hook.sol:99: Public Function Not Used Internally (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"74020d92640d611a718bb2f0c138eb24b7413cbed781039f3b093d5bcb64e0c9","verifiedTreeHash":"c2f0bbf0b5218ac51718a8d2e4da86ce1b0f43dc","verifierVersion":"0.1.0+c4d32abc"}]}