{"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":"2d993258-b0a8-44a4-9229-d2a23922ca19","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"88658584f1e568592586fc2181733ea2138ffee1702bcc61f7f903a5e22282f2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"6148c38e46f8d57ba9ea4ea238542296c8fb9f4c5f7733a4343058d044ffc09b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"34ad57171c16a3cc5889893a7ca695e87a119c8971e8f5048631a2e6693c4a32","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"780759532b72884df88be6e2d31807b7671fd406c28b824fc02c713565491e48","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"b47c1bd9357989ba54c486539fdadcbd202d17f6c63371a56eeed24474a4db16","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"96971422ca25f21d757bdf8f66f1b72fc3bbced9421d4f9fe7552cec2ed7d754","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"85b55c9a679dfe7d3ac0dbb7aa0ab0c16c6226b55276c0104f2267ea11c4d100","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"05421120e2138093285f13cae2df24b25f6139ede37f069c43bc9c5b369b6a1c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Launch PLEA ($PLEA), a meme token on Uniswap v4 with a custom hook. X: @plea_imd. Pair with IMD (pairWith: imd); fees in IMD.\n\nCORE MECHANIC\n- Buying is always open. Selling requires an approved plea.\n- Sellers submit a plea (text) on-chain, directly or via a \"Letterbox\" contract.\n- Each plea is judged by \"the Cabal\": a panel of 30 IMD oracle judges. Each judge scores 0-45; the median score counts (17 of 30 must agree).\n- Required score = 71 minus fact points (verifiable facts in the plea lower the bar).\n- APPROVED: the seller gets a 7-minute window to sell through the hook.\n- DENIED: one appeal is allowed after 4 hours.\n- OBJECTIONS: anyone can object to a sale within 20 minutes after it executes.\n\nSEASONS\n- Seasons with a leaderboard; the top 25 split the season prize pot.\n- Seat holders get a 1.2x score multiplier.\n\nDEAD-MAN SWITCH\n- If the Cabal returns no verdict for 33 hours, PLEA trades freely forever (the sell restriction is permanently disabled).\n\nREQUIREMENTS\n- The hook enforces sell gating on swaps; buys are never blocked.\n- Emit events for plea submitted, verdict, appeal, objection, sale window opened/closed, season end, dead-man switch triggered.\n- Expose view functions for the frontend: plea status, sale window expiry, current season, leaderboard, dead-man timer.\n- Run an independent adversarial security review of the hook (sell-gate bypass, oracle manipulation, timing/reentrancy, griefing of objections/appeals).\n\nToken name: PLEA\nToken symbol: PLEA","parentJobId":null,"planHash":"e853939089c575ef68ac919c763ef3bbe46e3e4f3f9fe2f76b7e14d77e417d1d","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"2d993258-b0a8-44a4-9229-d2a23922ca19","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2718-plea"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52614","feedbackHash":"8e8daa07ccf6fd39e4a330ccab14a80e7f102fe26eeaebaa349bee99dc1e1c56","nodeKey":"audit_economics","submissionHash":"88658584f1e568592586fc2181733ea2138ffee1702bcc61f7f903a5e22282f2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51445","feedbackHash":"d1dd59cecf56f22a1943b439304f790cf528f78f0d22b585ef15c386572d5e43","nodeKey":"audit_flow","submissionHash":"6148c38e46f8d57ba9ea4ea238542296c8fb9f4c5f7733a4343058d044ffc09b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"55247","feedbackHash":"e6274b61554b1f1ce7df31948bb6bd03c4ad35fc018139663883531825c4cf9c","nodeKey":"audit_judge","submissionHash":"6a0d4e0ab4edd8f5d9b9d8eb015abaccd88cefed7c035d8e5d55627f9e943346","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53924","feedbackHash":"4272a5ce1f93e8c915dac9d05d371ecc1f6f704f5cf87c1b48f8c9bb409f2535","nodeKey":"audit_judge","submissionHash":"34ad57171c16a3cc5889893a7ca695e87a119c8971e8f5048631a2e6693c4a32","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51872","feedbackHash":"11de2acb5664cc4c0fea60cb9a2a54b46c698e40ceb8a3457263120811e752d0","nodeKey":"audit_judge","submissionHash":"fb4b081831473ba276e40dfec461fee71e7a788bf1c86c2dafadc9574681e23c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51163","feedbackHash":"ce1405911f966846fae23390c6051314409e7793432ba119739adeb1917f1adc","nodeKey":"audit_math","submissionHash":"780759532b72884df88be6e2d31807b7671fd406c28b824fc02c713565491e48","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52586","feedbackHash":"f2845ba4e2d1be4a81e04d79e84291bc59d303dbcd1dbe7a3c75d0c142719162","nodeKey":"audit_permissions","submissionHash":"b47c1bd9357989ba54c486539fdadcbd202d17f6c63371a56eeed24474a4db16","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51188","feedbackHash":"a3d76766b036b199d705c8fbd4acf338b15f90ae795b9764e6230d73a0b08055","nodeKey":"build_contract_project","submissionHash":"1f98eb30ea852f25718869bd467dcf9e0c9b4c8ddc0b92bbf4a33b0d581556b4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52191","feedbackHash":"75ec5a993980a6f27e69094b2bda758e50471e7d60dfbd8799b9cad3f98e54a3","nodeKey":"build_contract_project","submissionHash":"9c7a576f9769d697670cbbb2c6f9aa47353403c2baedb6a2634b203e49ab7adf","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52166","feedbackHash":"237f663f08aeea00ef1c013d601fadd672b4b8bcd5e5eb309e2b7d8444480258","nodeKey":"build_contract_project","submissionHash":"96971422ca25f21d757bdf8f66f1b72fc3bbced9421d4f9fe7552cec2ed7d754","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51873","feedbackHash":"2168379cba067af73151d6f3e4590ee379960f203e3058edbdf775e33278c2e7","nodeKey":"manifest","submissionHash":"85b55c9a679dfe7d3ac0dbb7aa0ab0c16c6226b55276c0104f2267ea11c4d100","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51488","feedbackHash":"beb4ed676d42c0fafdc5ccf901fce21f8b610dc212dbd6a8ec4ed9543d9efe75","nodeKey":"manifest","submissionHash":"958490d37837dacbadbc461753b700c442e12c7c042bdeb8ef769cc9bf4d4375","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51492","feedbackHash":"0395767b8000113d513b4986b23215e33ce6006af1605a459c5b09ff42c32020","nodeKey":"manifest","submissionHash":"4d761f4d0f282c9b2b9c38fe5f5245fbc1578a5017d7a7101a51fde824073b98","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51316","feedbackHash":"54f92a0e8af276d0437a123a11caee19643e5d2d9753aa47edc70b9c6aaa2eba","nodeKey":"write_foundry_tests","submissionHash":"05421120e2138093285f13cae2df24b25f6139ede37f069c43bc9c5b369b6a1c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52682","feedbackHash":"9d1398e4c19e077f964599267510739c6cd68fec28e473610f02a22f2fcd9882","nodeKey":"write_foundry_tests","submissionHash":"418aa5337c99b1edf58f05eed2694bdf34644b5a0fa3c40adf55b1050c236788","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51157","feedbackHash":"52f0f6d42d6d13790a000f25e6b282330d0f9d37c1989786fe71e70f320818f2","nodeKey":"write_foundry_tests","submissionHash":"336dd32d5da547112346f75fc46dc15c93c548e70961c2f6b79a65ea3de0967d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"1ddcb78668821d5bdc98185c3dafcec33b910e505d882c98fc21eb58cf8d7aa6","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"2f48503592a73652c5cee57150a25526fd14fd183291a57298fabbe938034b9d","device":"6b0a28df3d585600","findings":[],"hash":"05421120e2138093285f13cae2df24b25f6139ede37f069c43bc9c5b369b6a1c","nodeId":"82ab0015-69b4-4c3c-abe2-6e087ae00405","outcome":"completed","summary":"The implementer fixed both findings from the last round, and my tests now confirm both fixes. I'm reporting no new defects this round, so there is no `.imd-findings.json`. The full suite passes with the verifier's seed: `forge build` is clean and `forge test` runs 158 tests in 16 suites with none failing or skipped. Nothing is committed.\n\n**The two fixes**\n- **End of season while the dead-man switch is due.** This was my low finding last round: `endSeason` could roll the prize pot over instead of sending it to the owner. I added tests in `test/PleaCourtRevision.t.sol`:\n  - If the switch falls due at the exact second the season ends, `endSeason` now sends the pot to the owner. This test would fail on the old code.\n  - If it falls due one second later, the pot rolls over once and goes to the owner at the next season's end.\n  - With a leaderboard, the winners are paid first and only the rest goes to the owner.\n- **A judge's own plea stuck at 29 votes.** A judge can't vote on their own plea, so a split vote used to leave it pending forever. It is now decided once all 29 other judges have voted. I added a fuzz test (1,000 runs) over every split where neither side reaches 17, in random vote order. It checks the plea stays pending through 28 votes and is denied on the 29th. A second test shows that if the owner swaps the judge-seller off the panel mid-round, the plea goes back to needing 30 votes and waits for the replacement judge.\n\n**One test-side change the fix required**\nThe invariant test's model of `endSeason` in `test/helpers/CourtHandler.sol` decided whether the pot goes to the owner by checking `deadManTriggered()`. That matched the old, buggy behaviour, so it now uses `tradesFreely()`. It still passed against the fixed code, which means the random sequences rarely hit that case.\n\n**Not reported as a finding**\nThe owner can still change the judge panel mid-vote. For example, a panel made of the 29 judges who have already voted plus the seller leaves nobody able to vote, so the plea sits pending until the dead-man switch fires. `SECURITY.md` already lists this as an owner power, with another panel change as the fix.\n\nI updated `test/README.md` with this round's notes and removed the line saying the end-of-season finding was still open.","treeHash":"2fd0bec42f1c3f3276102d81825e6c438c4853c7","usage":{"cachedInputTokens":692588,"inputTokens":29,"model":"claude-opus-5-5","outputTokens":11809,"runtime":"claude","turns":16,"wallClockMs":259326}},{"artifacts":[],"attempt":1,"bundleHash":"f15e2e836c8ac33c5e4ffdb965fd1ae8cd518c9dd914213a63405cd09dac3fe2","device":"0b0761c9b10fe0bb","findings":[],"hash":"1f98eb30ea852f25718869bd467dcf9e0c9b4c8ddc0b92bbf4a33b0d581556b4","nodeId":"a87c4e27-252b-4c94-9512-a26f365dbc33","outcome":"completed","summary":"All eight reopening findings reproduced and are fixed, every advisory finding is answered, and the suite passes under the verifier's fuzz seed.\n\n**Reproduction.** The three reviewer proofs all failed on the starting tree with the exact messages reported. I reproduced the five other defects by reading the code paths and then covering each with a new test that fails without the change.\n\n**What changed in `src/PleaCourt.sol`:**\n- A hung Cabal (all 30 voted, no side at 17) now denies the plea with the median of all 30, resets the dead-man clock, and leaves the appeal open.\n- A pending plea cannot be withdrawn once a judge has scored it, so mid-vote withdrawals cannot starve the Cabal of verdicts.\n- No plea is accepted until the Cabal is installed, so the clock cannot start on an empty panel.\n- A denial records an earliest-next-plea time and marks the seller's next plea as their one re-hearing. A failed re-hearing starts a 24-hour cooldown, a new named constant.\n- Bans are checked in `authorizeSale` and `appeal`, and an upheld objection strikes whatever plea the seller has in flight, with a new `Struck` status.\n- Each sale carries the season of its points. A strike after the season end forfeits that season's prize credit. Prizes are now locked for 3 days after the season end and paid by a new `claimPrize`, which is longer than the full objection timeline.\n- The board keeps 50 entries so a struck winner is replaced by the next one. The top 25 are paid.\n- After the dead-man switch, free sells pay no pot fee and each `endSeason` credits the unclaimed pot to the owner.\n- Also: recusal for judges on their own pleas and sales, no self-objection, rulings refused after 48 hours, and a proper `AmountTooLarge` error.\n\n**Other files.** The hook now settles sales to the PLEA actually consumed and drops the unreachable transient flag. The letterbox gained `invalidateNonce`. The interface gained the season fields and `Struck`.\n\n**One finding disputed.** Points awarded at approval rather than at sale stay as designed, because the reviewer's own held proof asserts points right after the 17th vote. This is documented in the README limitations.\n\n**Checks.** `forge build`, 92 tests passing with the verifier's seed, `forge fmt --check` clean. The court runtime is 20.7 KB and the hook initcode 36.6 KB, both under their limits. README and SECURITY.md are updated, and `.imd-responses.json` answers all 17 findings.","treeHash":"0166102283b0eb3929c6eaf7630f8d1921636c94","usage":{"cachedInputTokens":4607364,"inputTokens":962,"model":"claude-fable-5-1","outputTokens":90616,"runtime":"claude","turns":92,"wallClockMs":1143448}},{"artifacts":[],"attempt":1,"bundleHash":"8da92d7d0bb8ac9aab2de26c30f64a704b21077a200926fa5320edcb3917d272","device":"a6d6df9f16a14651","findings":[{"description":"The board stores only its current top 25. _strike removes an upheld-objection seller but never promotes the next eligible seller whose points are already recorded in seasonPoints. With 26 positive-scoring sellers, striking one listed winner leaves 24 board entries even though 25 eligible sellers remain. endSeason iterates only this incomplete board, so the omitted seller receives no prize and the remaining winners receive its share. Later low-scoring newcomers can also occupy the vacant slot ahead of the omitted higher-scoring seller. The task promises that the top 25 split the season prize pot.","line":524,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {PLEAToken} from \"src/PLEAToken.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract LeaderboardRemovalProof is Test {\n    function owner() external view returns (address) { return address(this); }\n\n    function test_removingWinnerMustPromoteNextEligibleSeller() public {\n        PLEAToken pair = new PLEAToken();\n        PleaCourt court = new PleaCourt(address(this));\n        court.bind(address(pair));\n        address[] memory judges = new address[](30);\n        for (uint256 i; i < 30; ++i) judges[i] = makeAddr(string.concat(\"judge\", vm.toString(i)));\n        court.setJudges(judges);\n        address[] memory players = new address[](26);\n        for (uint256 i; i < 26; ++i) {\n            players[i] = makeAddr(string.concat(\"player\", vm.toString(i)));\n            vm.prank(players[i]);\n            uint256 id = court.submitPlea(\"facts\", 1);\n            for (uint256 j; j < 17; ++j) {\n                vm.prank(judges[j]);\n                court.judge(id, uint8(20 + i), 71);\n            }\n        }\n        assertEq(court.leaderboard().length, 25);\n        assertEq(court.seasonPoints(1, players[0]), 20);\n        uint256 sale = court.authorizeSale(players[25], 1);\n        pair.approve(address(court), 1 ether);\n        uint256 oid = court.object(sale);\n        for (uint256 j; j < 17; ++j) {\n            vm.prank(judges[j]);\n            court.ruleObjection(oid, true);\n        }\n        // 25 eligible positive-scoring sellers remain; the top 25 must include all of them.\n        assertEq(court.leaderboard().length, 25, \"eligible runner-up lost after winner struck\");\n        pair.transfer(address(court), 100 ether);\n        vm.warp(court.seasonStart() + 30 days);\n        court.endSeason();\n        assertGt(court.claimable(players[0]), 0, \"eligible 25th seller excluded from season prize\");\n    }\n}","reproduction":"Run the attached self-contained test. Approve 26 sellers with scores 20 through 45 (17 judges each, 71 fact points). The 20-point seller is initially rank 26. Authorize a sale by the 45-point seller, post a 1-token objection bond, and obtain 17 uphold votes. Expected: 25 eligible entries including the 20-point seller, who receives a positive prize after funding and ending the season. Actual: leaderboard().length is 24, failing with \"eligible runner-up lost after winner struck: 24 != 25\". No owner abuse or nonstandard token is involved. The proof uses a standard 18-decimal project token as the local pair asset and the test contract as the authorized court driver.","severity":"medium","title":"An upheld objection drops the leaderboard below the eligible top 25 and excludes a prize winner"},{"description":"sell calls court.authorizeSale with the requested amount before the PoolManager reports actual consumption. It refunds unconsumed PLEA afterwards, but never reconciles Plea.sold, Sale.amount, or an already closed sale window. A valid approved seller can lose their remaining seven-minute allowance when liquidity runs out, even though most tokens were refunded. SaleRecorded and the objection record also disagree with SellExecuted. An empty pool with minOut=0 is the zero-consumption edge of the same defect. Authorization must reserve safely before external interactions and finalize or release the reservation using actual consumed input.","line":161,"path":"src/PleaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {PLEAToken} from \"src/PLEAToken.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract PartialFillProof is Test, IUnlockCallback {\n    PoolManager internal manager;\n    PoolKey internal key;\n    PleaHook internal hook;\n    PLEAToken internal token;\n    PLEAToken internal pair;\n\n    function test_saleAccountingMustChargeOnlyConsumedTokens() public {\n        manager = new PoolManager(address(this));\n        token = new PLEAToken();\n        pair = new PLEAToken();\n        bytes memory code = abi.encodePacked(type(PleaHook).creationCode,\n            abi.encode(manager, address(token), address(this), address(this)));\n        bytes32 hash = keccak256(code);\n        for (uint256 salt; salt < 200_000; ++salt) {\n            address predicted = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hash)))));\n            if (uint160(predicted) & 0x3fff != 0x2880) continue;\n            hook = new PleaHook{salt: bytes32(salt)}(manager, address(token), address(this), address(this));\n            break;\n        }\n        require(address(hook) != address(0), \"mining failed\");\n        bool zero = address(token) < address(pair);\n        key = PoolKey(Currency.wrap(zero ? address(token) : address(pair)),\n            Currency.wrap(zero ? address(pair) : address(token)), 12500, 60, IHooks(address(hook)));\n        manager.initialize(key, 79228162514264337593543950336);\n        manager.unlock(\"\"); // Deliberately narrow, valid liquidity range: a partial fill is reachable.\n        PleaCourt court = hook.court();\n        address[] memory judges = new address[](30);\n        for (uint256 i; i < 30; ++i) judges[i] = makeAddr(string.concat(\"judge\", vm.toString(i)));\n        court.setJudges(judges);\n        uint256 requested = 1 ether;\n        uint256 plea = court.submitPlea(\"sell within approved allowance\", requested);\n        for (uint256 i; i < 17; ++i) {\n            vm.prank(judges[i]);\n            court.judge(plea, 45, 26);\n        }\n        token.approve(address(hook), requested);\n        uint256 beforeBalance = token.balanceOf(address(this));\n        (uint256 sale, uint256 proceeds) = hook.sell(requested, 1);\n        uint256 consumed = beforeBalance - token.balanceOf(address(this));\n        assertGt(proceeds, 0);\n        assertGt(consumed, 0);\n        assertLt(consumed, requested, \"fixture must exercise a real partial fill\");\n        assertEq(court.plea(plea).sold, consumed, \"refunded tokens incorrectly spend the plea allowance\");\n        assertEq(court.sale(sale).amount, consumed, \"objection record reports tokens never sold\");\n        (, uint256 remaining) = court.saleWindow(address(this));\n        assertEq(remaining, requested - consumed, \"partial fill prematurely closes the sale window\");\n    }\n\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (BalanceDelta d,) = manager.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 1e18, 0), \"\");\n        pay(key.currency0, d.amount0());\n        pay(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function pay(Currency currency, int128 delta) internal {\n        if (delta < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(uint128(-delta)));\n            manager.settle();\n        }\n    }\n}","reproduction":"Run the attached self-contained test against a real local PoolManager, a correctly mined PleaHook, and two standard tokens. Initialize at price 1:1 with fee 12500 and spacing 60; add 1e18 liquidity only in ticks [-60, 60]. Approve a 1e18 PLEA plea through 17 judges and call sell(1e18, 1). Actual input consumed is 3042383861004483 wei and the remainder is refunded. Expected: Plea.sold and Sale.amount equal actual consumed input and the unexpired window retains the remaining allowance. Actual: Plea.sold is 1000000000000000000, the sale record reports the full request, and the window is Closed. The assertion fails with \"refunded tokens incorrectly spend the plea allowance: 1000000000000000000 != 3042383861004483\".","severity":"medium","title":"A partial swap consumes the entire plea allowance and records refunded PLEA as sold"}],"hash":"336dd32d5da547112346f75fc46dc15c93c548e70961c2f6b79a65ea3de0967d","nodeId":"82ab0015-69b4-4c3c-abe2-6e087ae00405","outcome":"completed","summary":"Added adversarial, fuzz, and stateful invariant tests under `test/`.\n\n`forge build` and `forge test` pass with the supplied seed: **117 passed, 0 failed**.\n\nReported two medium-severity defects in `.imd-findings.json`, with reproduced failing proofs:\n\n- Leaderboard removal excludes an eligible prize winner.\n- Partial fills consume allowance for refunded tokens.\n\nReview and coverage details are in `test/README.md`.","treeHash":"531e067ffc1778bfb704be332372babb509628d4","usage":{"cachedInputTokens":3259520,"inputTokens":128202,"model":"gpt-6-astra","outputTokens":41721,"runtime":"codex","turns":8,"wallClockMs":1145207}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7ac9f7a7687c25e","findings":[],"hash":"34ad57171c16a3cc5889893a7ca695e87a119c8971e8f5048631a2e6693c4a32","nodeId":"2e3806a9-8ced-42c3-9d6a-9eb9179b56b9","outcome":"completed","summary":"Both earlier findings are settled as fixed, and no new defect was found. The review output is in `.imd-findings.json` with an empty findings list and a coverage row for all 31 entry points plus two invariant rows.\n\n**Judge's own plea hang (medium, d38c1c69): fixed.** The hung-Cabal rule in `judge()` now compares the vote total against the new `eligibleJudges(pleaId)` view, which returns 29 when the seller sits on the panel. I ran my attached proof as the author described, and it fails only at its own revert expectations: the re-vote now reverts `WrongStatus(3)` because the plea is Denied, which is the outcome the finding asked for. My own test confirms the 15/14 split on a judge's plea is denied at the 29th vote, pendingCount returns to zero, the dead-man deadline is zero, trading is still gated 33 hours later, and a round-2 appeal also settles at 29. An outsider's plea still waits for the 30th vote. The author's dispute of the proof is correct, so the finding is dropped rather than restated.\n\n**endSeason not latching a due switch (low, 5ab39671): fixed.** `endSeason()` now calls `_latchDeadMan()` right after the season-length check and before reading the pot. I reproduced the earlier scenario on the revised code: the switch latches inside endSeason, the owner is credited the full 5e18 pot, prizePot is zero, and `claim()` pays it out.\n\n**Checks run this round:**\n- Full suite: 158 tests pass across 16 suites, including the author's two new regression tests.\n- Four scratch tests of my own covering both fixes and a panel-rotation edge after 28 votes on a judge-seller plea, all passing. Scratch files were deleted afterwards and the working tree is clean apart from the findings file.\n- Slither and Aderyn leads reviewed: the strict equalities, uninitialized locals and timestamp comparisons are intentional comparisons against zero or deadline arithmetic, and the unused-error lead no longer applies since `CabalNotConfigured` is now used in `_submit`.\n\nNo manifest issue: launch.json matches the hook's constructor order, the three permissions match `getHookPermissions`, pool fee is 12500 and the token has no constructor arguments.","treeHash":null,"usage":{"cachedInputTokens":558288,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":12910,"runtime":"claude","turns":18,"wallClockMs":200351}},{"artifacts":[],"attempt":1,"bundleHash":"8922319f61438df8d51f606c0c4b8e1a03a397607d00d369971cce78456f79e7","device":"a0f4c3f900dedd4a","findings":[{"description":"endSeason decides between rolling the unpaid pot into the next season and crediting it to the hook's owner by reading the storage flag `deadManTriggered`, but unlike every other state-changing entry point it never calls `_latchDeadMan()`. Once the 33-hour silence has passed, `tradesFreely()` is true and nobody can earn points again (judge, submitPlea and appeal revert FreeTrading, and their own latch is rolled back with the revert), yet the flag stays false until someone calls triggerDeadManSwitch, withdrawPlea or ruleObjection. An endSeason in that gap rolls the pot for a whole 30-day season instead of sweeping it, contrary to the function's own documented rule. No funds are lost: the next endSeason after a latch sweeps the whole pot. Fix: call `_latchDeadMan()` at the top of endSeason.","line":555,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\n\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Plays the hook: the court reads `owner()` from it and only it may bind and authorize.\ncontract ProofHook {\n    address public owner;\n    PleaCourt public court;\n\n    constructor(address owner_, address imd) {\n        owner = owner_;\n        court = new PleaCourt(address(this));\n        court.bind(imd);\n    }\n}\n\ncontract DeadManSweepProof is Test {\n    function test_seasonEndAfterDueButUnlatchedSwitchSweepsThePot() public {\n        vm.warp(10 days);\n        address owner = makeAddr(\"owner\");\n        ProofIMD imd = new ProofIMD();\n        ProofHook hook = new ProofHook(owner, address(imd));\n        PleaCourt court = hook.court();\n        address[] memory panel = new address[](30);\n        for (uint256 i; i < 30; ++i) {\n            panel[i] = makeAddr(string.concat(\"judge\", vm.toString(i)));\n        }\n        vm.prank(owner);\n        court.setJudges(panel);\n\n        // A plea goes unanswered for 33 hours: the switch is due and sells trade freely for good.\n        vm.prank(makeAddr(\"seller\"));\n        court.submitPlea(\"please\", 1);\n        vm.warp(block.timestamp + 33 hours);\n        assertTrue(court.tradesFreely());\n        assertFalse(court.deadManTriggered()); // nobody has called a latching function yet\n\n        // The season ends with an empty board. Nobody can ever earn points again (judge reverts\n        // FreeTrading), so per endSeason's own rule the pot belongs to the hook's owner.\n        imd.transfer(address(court), 5e18);\n        vm.warp(court.seasonStart() + 30 days);\n        court.endSeason();\n\n        // Expected: 5e18 swept to the owner. Actual: claimable(owner) == 0 and the pot rolls a whole\n        // season, because endSeason reads `deadManTriggered` without latching the due switch first.\n        assertEq(court.claimable(owner), 5e18, \"due switch not latched: pot rolled instead of swept\");\n        assertEq(court.prizePot(), 0);\n    }\n}","reproduction":"30 judges set; one plea submitted and left unscored for 33 hours (tradesFreely()==true, deadManTriggered()==false); 5e18 IMD sent to the court; warp to seasonStart+30 days; endSeason(). Expected: claimable(owner)==5e18 and prizePot()==0. Actual: claimable(owner)==0 and prizePot()==5e18 (rolled into season 2).","severity":"low","title":"endSeason does not latch a due dead-man switch, so the post-switch pot rolls over instead of being swept to the owner"}],"hash":"418aa5337c99b1edf58f05eed2694bdf34644b5a0fa3c40adf55b1050c236788","nodeId":"82ab0015-69b4-4c3c-abe2-6e087ae00405","outcome":"completed","summary":"The test suite now passes against the revised implementation: 150 tests pass with the verifier's fuzz seed. The only failing test is my proof in `test/scratch/`, which is supposed to fail and is deleted before verification. Only files under `test/` changed, and nothing was committed.\n\n**Why 5 tests broke.** The implementation was revised to fix the two defects I reported last round, and it also changed some rules on purpose. I updated my tests to the new rules instead of rewriting them:\n- **Test driver** (`test/helpers/CourtFixture.sol`): a sale is now reserved first and then settled to the amount the pool actually took, all in one call, the way the hook does it. I added helpers for partial fills and direct settling.\n- **`PleaCourtAdversarial.t.sol`**:\n  - An amount above the uint128 limit now reverts with `AmountTooLarge`.\n  - A 15/15 vote is now a \"hung\" denial that can be appealed once. I kept a 15/14 version that stays pending until the 33-hour dead-man deadline.\n  - Season prizes are now locked credits, so the prize-rounding fuzz test also checks the 3-day lock boundary and that a prize pays out only once.\n- **Invariant handler and invariants**: the random-sequence campaign now covers partial fills, re-hearings and cooldowns, refused withdrawals once votes are cast, banned sellers' pleas being struck, the 48-hour ruling period, prize claims, forfeiture of a prize from an already-ended season, and the post-switch sweep to the owner. The money checks now include locked prizes and the owner's balance. It ran 256 sequences of 64 calls each with no unexpected reverts.\n\n**New file `test/PleaCourtRevision.t.sol` (9 tests, one is a 1,000-run fuzz):**\n- Both earlier findings are now passing regression tests:\n  - Striking the leader among 26 sellers promotes the 26th, who then gets a prize.\n  - On a partial fill, the unused part goes back to the seller's window.\n- It also tests attempts to get around the new rules:\n  - getting a second appeal by withdrawing and resubmitting, including through the Letterbox;\n  - the 4-hour and 24-hour cooldowns at their exact boundaries;\n  - withdrawing a plea that judges have started scoring;\n  - a banned seller trying to use the rest of their sale window.\n- One test checks the latest possible ruling: it still lands before prizes unlock, and it forfeits the guilty seller's prize back to the pot.\n\n**New finding (low), in `.imd-findings.json` with a runnable failing proof.** `endSeason` (`src/PleaCourt.sol:555`) doesn't record a dead-man switch that is already due. Say 33 hours pass with no verdict, but nobody calls a function that records the switch before the season ends. The pot then rolls into the next season instead of going to the owner, for up to 30 days. No funds are lost: the next season end pays the whole pot to the owner. The fix is to call `_latchDeadMan()` at the start of `endSeason`. No passing test treats the current behaviour as correct.\n\n`test/README.md` now describes this revision round.","treeHash":"7f6d1efc34ca95d54b3a0547a43d071fa6da9677","usage":{"cachedInputTokens":2547487,"inputTokens":59,"model":"claude-opus-5-5","outputTokens":37314,"runtime":"claude","turns":32,"wallClockMs":446179}},{"artifacts":[],"attempt":1,"bundleHash":"57a7eebe55056313d13615206629e8fe11bb7dec0d09e25e186cdb58e5cb8a17","device":"be85220723d9ce9d","findings":[],"hash":"4d761f4d0f282c9b2b9c38fe5f5245fbc1578a5017d7a7101a51fde824073b98","nodeId":"b82ba0cc-b1c6-4cba-b324-7e6afd61cb8f","outcome":"completed","summary":"I updated `launch.json` so its notes match the current hook code. It was the only file I changed. `forge build` succeeds and `forge test`, run with the verifier's fuzz seed, passed all 89 tests.\n\n**What I checked against the code:**\n- **Contracts:** the hook is `PleaHook` and the token is `PLEAToken`, named as in the repository.\n- **Hook constructor arguments:** `[\"$poolManager\", \"$token\", \"$owner\", \"$factory\"]`, which matches the constructor's parameter order.\n- **Permissions:** `beforeInitialize`, `beforeAddLiquidity` and `beforeSwap`, exactly what `getHookPermissions()` returns. `beforeInitialize` accepts calls only from the PoolManager, and only when the launch factory is opening the pool.\n- **Pool:** paired with IMD at `0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7`, fee 12500, tickSpacing 60, initialPrice `79228162514264337593543950336`. The hook does not restrict the fee, so it accepts 12500.\n- **Token:** PLEA / PLEA, 18 decimals.\n\n**What I fixed:** the notes still described an older way of gating sells, where the hook marked its own sells and let them through. The accepted code now blocks every sell that reaches `beforeSwap` until the dead-man switch fires. Plea-approved sales go only through the hook's own `sell()`, whose swap Uniswap v4 doesn't send back to the hook's callbacks (`noSelfCall`). The notes now also say that `sell()` only charges the plea window for the PLEA the pool actually used and refunds the rest. They also say that no pot fee is taken once trading is free. The notes are 3,544 characters, under the 4,000 limit.\n\nI left the change uncommitted, since I wasn't asked to commit.\n\nThe requester still has to supply the 30 Cabal judges' oracle addresses. The owner sets them after launch, and if they aren't set before the first plea or within 33 hours of it, the dead-man switch fires and PLEA trades freely forever. The notes already say this.","treeHash":"bf0220af620af48b747b86a78bde664b4d698115","usage":{"cachedInputTokens":152098,"inputTokens":10,"model":"claude-opus-5-5","outputTokens":2099,"runtime":"claude","turns":5,"wallClockMs":53756}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa8fc4653a9e883d","findings":[{"citation":"resolved","description":"judge() only settles a plea when one side reaches QUORUM (17). Nothing handles the case where all 30 judges have voted and neither side has 17 (16/14, 15/15, 14/16). Every judge is then refused with AlreadyVoted, the status stays Pending so the seller cannot appeal (WrongStatus) and only the seller can withdraw it, which a seller who prefers free trading will not do. The plea keeps pendingCount > 0, and deadManDeadline() = max(pendingSince, lastVerdictAt) + 33h keeps running; the only way to reset it is a verdict (_decide), which the Cabal cannot produce on this plea. If it is the only plea in flight for 33 hours (a quiet day, a weekend), tradesFreely() turns true and the sell gate is removed permanently (the latch is irreversible by design), although every judge voted within minutes. Honest judging on a borderline plea produces such a split routinely; no misbehaviour is needed. The owner's only rescue is setJudges() with a panel containing at least one address that has not voted, so that a 31st vote reaches 17, which has to happen inside the 33h and is undocumented. The brief's '17 of 30 must agree' says nothing about what happens when they do not; the asymmetry with objections is telling: ruleObjection has a 48h timeout (reclaimObjectionBond) while a plea has none.","line":317,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\n/// @dev Stands in for PleaHook: the court only needs `owner()` from it for administration.\ncontract HookStub {\n    address public owner;\n\n    constructor(address owner_) {\n        owner = owner_;\n    }\n}\n\n/// @notice A plea on which all 30 judges vote without 17 agreeing is stuck Pending forever: no judge can\n/// vote again, the seller cannot appeal, and the Cabal cannot produce any verdict that would reset the\n/// dead-man clock. 33 hours later the sell gate is gone for good although every judge did their job.\n/// Fails on the current code (the plea is still Pending after 30 votes); passes once the court settles\n/// a plea when the whole Cabal has voted.\ncontract DeadlockTest is Test {\n    PleaCourt court;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address[] judges;\n\n    function setUp() public {\n        HookStub hook = new HookStub(owner);\n        court = new PleaCourt(address(hook));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function test_splitCabalCannotReachAVerdictAndTheDeadManFires() public {\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"borderline\", 1e18);\n\n        // 16 approve (45 + 26 = 71), 14 deny (44 + 26 = 70): a majority of neither side.\n        for (uint256 i = 0; i < 16; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 45, 26);\n        }\n        for (uint256 i = 16; i < 30; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 44, 26);\n        }\n\n        // All 30 judges voted: the plea must have a verdict and must no longer count as pending.\n        assertTrue(\n            court.pleaStatus(id) != IPleaCourt.Status.Pending,\n            \"30 votes cast, 16 approve / 14 deny: the plea has no verdict and stays Pending\"\n        );\n        assertEq(court.pendingCount(), 0, \"an undecidable plea keeps the dead-man clock running\");\n        assertEq(court.deadManDeadline(), 0, \"no plea is pending, so no deadline should run\");\n\n        // Nothing the Cabal can do resets the clock while this is the only plea; 33h later the gate is gone.\n        vm.warp(block.timestamp + 33 hours);\n        assertFalse(court.tradesFreely(), \"the sell gate was lost although the whole Cabal voted\");\n    }\n}","reproduction":"State: Cabal of 30 configured, one plea pending. Calls: seller.submitPlea(text, 1e18); judges[0..15].judge(id, 45, 26) (45+26=71, approve); judges[16..29].judge(id, 44, 26) (70, deny). Expected: after 30 votes the plea has a verdict (median of 30 scores is well defined) or is at least denied so the seller can appeal, and pendingCount returns to 0. Actual: pleaStatus(id) == Pending, approveVotes 16, denyVotes 14, any further judge() reverts AlreadyVoted, appeal(id) reverts WrongStatus(Pending), pendingCount == 1, deadManDeadline() == submit + 33h. vm.warp(+33h): tradesFreely() == true, and the next state-changing call latches deadManTriggered forever. Fix: when approveVotes + denyVotes == JUDGE_COUNT (or == the number of installed judges) without a quorum, settle the plea (deny it so the appeal path exists, or decide by the median against the bar as the brief words it) and call _leavePending; alternatively give every plea its own deadline after which it is denied. Proof test: test/scratch/Deadlock.t.sol fails on this code at the first assertion.","severity":"high","snippet":"        if (p.approveVotes >= QUORUM) _decide(pleaId, p, true);\n        else if (p.denyVotes >= QUORUM) _decide(pleaId, p, false);","title":"A 30-vote split with no 17-majority leaves the plea Pending forever and lets the dead-man switch fire on an active Cabal"},{"citation":"resolved","description":"bannedUntil is checked in _submit only (line 278). appeal() re-enters a denied plea into the Cabal's queue with no ban check, and authorizeSale() spends an Approved window with no ban check either. An upheld objection therefore does not stop the seller it bans from selling: (a) a seller whose previous sale is objected to while a second plea of theirs sits Denied appeals it four hours later and, if round 2 approves, receives a 7-minute window during the ban; (b) a seller banned while their window is still open (objection and 17 rulings inside the 7 minutes, scratch test BanWindow.t.sol) keeps selling the remainder of the approved amount. The ban is the only sanction the objection mechanism carries, so this is the objection flow's guarantee being partly void. The judges can still deny the appeal, which is why this is low rather than medium.","line":341,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract IMDMock is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Stands in for PleaHook: owner() for administration, and the hook-only calls bind/authorizeSale.\ncontract HookStub {\n    address public owner;\n\n    constructor(address owner_) {\n        owner = owner_;\n    }\n\n    function bind(PleaCourt court, address imd) external {\n        court.bind(imd);\n    }\n\n    function authorize(PleaCourt court, address seller, uint256 amount) external returns (uint256) {\n        return court.authorizeSale(seller, amount);\n    }\n}\n\n/// @notice A seller banned by an upheld objection is only refused at submitPlea. The ban does not touch\n/// a denied plea they already hold: appeal() opens round 2 for a banned seller, and an approval there\n/// gives the banned seller a sale window. Fails on the current code (the appeal goes through); passes\n/// once appeal() refuses a banned seller.\ncontract BanBypassTest is Test {\n    PleaCourt court;\n    HookStub hook;\n    IMDMock imd;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address objector = makeAddr(\"objector\");\n    address[] judges;\n\n    function setUp() public {\n        hook = new HookStub(owner);\n        court = new PleaCourt(address(hook));\n        imd = new IMDMock();\n        hook.bind(court, address(imd));\n        imd.transfer(objector, 10e18);\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function _score(uint256 id, uint8 s, uint8 facts) internal {\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, s, facts);\n        }\n    }\n\n    function test_bannedSellerCanAppealADeniedPlea() public {\n        // Plea B approved, sold in full: the sale is recorded and B closes.\n        vm.prank(seller);\n        uint256 b = court.submitPlea(\"first\", 1e18);\n        _score(b, 45, 26);\n        uint256 saleId = hook.authorize(court, seller, 1e18);\n        assertEq(uint8(court.pleaStatus(b)), uint8(IPleaCourt.Status.Closed));\n\n        // Plea A submitted and denied straight away.\n        vm.prank(seller);\n        uint256 a = court.submitPlea(\"second\", 1e18);\n        _score(a, 10, 0);\n        assertEq(uint8(court.pleaStatus(a)), uint8(IPleaCourt.Status.Denied));\n\n        // Within 20 minutes the sale is objected to, and the Cabal upholds: 7-day ban.\n        vm.startPrank(objector);\n        imd.approve(address(court), 1e18);\n        uint256 oid = court.object(saleId);\n        vm.stopPrank();\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.ruleObjection(oid, true);\n        }\n        uint64 until = court.bannedUntil(seller);\n        assertGt(until, block.timestamp + 6 days);\n\n        // A new plea is refused, as intended ...\n        vm.prank(seller);\n        vm.expectRevert(abi.encodeWithSelector(PleaCourt.Banned.selector, until));\n        court.submitPlea(\"third\", 1);\n\n        // ... but 4 hours later, still banned, the seller appeals the denied plea, which re-enters the\n        // Cabal's queue and can be approved into a sale window during the ban.\n        vm.warp(block.timestamp + 4 hours);\n        assertTrue(court.bannedUntil(seller) > block.timestamp, \"still banned\");\n        vm.prank(seller);\n        vm.expectRevert();\n        court.appeal(a);\n    }\n}","reproduction":"Calls (court bound to IMD, 30 judges): seller.submitPlea('first', 1e18); 17 judges judge(b, 45, 26) -> Approved; hook.sell(1e18) (court.authorizeSale(seller, 1e18)) -> saleId 1, plea b Closed; seller.submitPlea('second', 1e18); 17 judges judge(a, 10, 0) -> Denied; objector.object(1) with 1 IMD bond; 17 judges ruleObjection(1, true) -> bannedUntil[seller] = now + 7d. seller.submitPlea('third', 1) reverts Banned (correct). vm.warp(+4h); seller.appeal(a). Expected: refused while banned. Actual: succeeds, plea a is Pending in round 2 and an approval opens a sale window for the banned seller. Fix: check bannedUntil in appeal() (and, if the ban is meant to stop sales, in authorizeSale(), or close the seller's open window in ruleObjection's uphold branch). Proof test: test/scratch/BanBypass.t.sol fails on this code ('next call did not revert as expected').","severity":"low","snippet":"        if (msg.sender != p.seller) revert NotSeller();\n        if (p.status != Status.Denied) revert WrongStatus(p.status);\n        if (p.appealed) revert AlreadyAppealed();","title":"A ban only gates submitPlea: a banned seller can appeal a denied plea into a new sale window, and an open window keeps selling after the ban"},{"citation":"resolved","description":"authorizeSale(msg.sender, amountIn) runs before the swap and writes p.sold += amountIn and Sale.amount = amountIn (PleaCourt.sol:218-226), closing the window when p.sold == p.amount. The swap is exact input with the price limit at MIN/MAX_SQRT_PRICE, so when in-range liquidity runs out the pool consumes less than amountIn and line 167 refunds the rest to the seller. The court is never told: the seller's approved amount is spent on PLEA they still hold, the window may be Closed, and the Sale (the object of objections) records a larger amount than was sold. The seller must plead again for tokens the Cabal already approved. Self-harm to the seller and a wrong public record; no third-party loss, hence low.","line":161,"path":"src/PleaHook.sol","reproduction":"Fixture: PleaBase; factory.modify(key, minTick, maxTick, -1e21) then factory.modify(key, -600, 600, 1e18) so the pool holds a thin position. submitAndApprove(seller, 1e21); seller approves and calls hook.sell(1e21, 0). Expected: the window is charged what the pool consumed. Actual (scratch test PartialFill.t.sol): seller's PLEA balance falls by 30838469241430642 (~0.031 PLEA), yet court.plea(id).sold == 1e21, status Closed, court.sale(1).amount == 1e21, saleWindow(seller) == (0, 0). Fix: authorize before the swap for the ceiling but record consumed after it (e.g. a second hook-only call that writes p.sold/Sale.amount from `consumed` and closes the window only when consumed reaches p.amount), or revert when consumed < amountIn.","severity":"low","snippet":"        if (!free) saleId = court.authorizeSale(msg.sender, amountIn);","title":"sell() charges the whole amountIn to the plea window and the Sale record before the swap; a partial fill burns the window on PLEA that was refunded"},{"citation":"resolved","description":"object() admits exactly one objection per sale, whoever is first. The seller (or a contract that calls hook.sell and court.object in the same transaction) objects to their own sale in the block it executes, posting the 1 IMD bond. Every later object(saleId) from a third party reverts AlreadyObjected, so the brief's 'anyone can object within 20 minutes' holds only for the first caller. The judges can still uphold the self-objection, so the sanction is not removed outright; but the bond comes back to the seller when upheld or when unruled for 48h, and if the Cabal does not rule within 48h the seller reclaims it, the objection is marked resolved, ruleObjection reverts ObjectionResolvedAlready and the sale can never be objected to again. The cost to the seller is 1 IMD only when the Cabal actively dismisses.","line":390,"path":"src/PleaCourt.sol","reproduction":"Calls: seller.submitPlea -> 17 approvals -> hook.sell(1e18) (saleId 1); seller approves 1 IMD and calls court.object(1) in the same block; stranger.object(1). Expected: the stranger can object within 20 minutes. Actual: reverts AlreadyObjected (scratch test SelfObject.t.sol). Then vm.warp(+48h); seller.reclaimObjectionBond(1); seller.claim() returns the 1 IMD; judges[0].ruleObjection(1, true) reverts ObjectionResolvedAlready; bannedUntil[seller] == 0. Fix: refuse object() from s.seller, and/or allow several objections per sale (or one per objector) so a self-objection cannot occupy the slot.","severity":"low","snippet":"        if (s.objected) revert AlreadyObjected();","title":"One objection per sale lets the seller take the slot on their own sale and shut out every other objector"},{"citation":"resolved","description":"lib/v4-core/src/libraries/Hooks.sol:253 (`if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, lpFeeOverride);`) returns before calling the hook whenever the PoolManager's caller is the hook. The swap PleaHook.unlockCallback drives is therefore never seen by beforeSwap, so the transient SELLING_SLOT and the `sender == address(this) && _isSelling()` clause are dead code, and SECURITY.md's reasoning that the transient flag is what lets the hook's own sell through is inaccurate. Behaviour is still correct: the only poolManager.swap the hook ever issues is inside unlockCallback (onlyPoolManager) after authorizeSale, and the self-call exemption is what admits it. Reported so the documentation and the tests describe the real control flow; the gate for every other sender is intact (direction derived from zeroForOne, identity never read from hookData).","line":148,"path":"src/PleaHook.sol","reproduction":"Set a breakpoint or an event in beforeSwap and run test_sellInWindow: beforeSwap is not invoked for the hook's own swap. Equivalently, replace `_isSelling()` with `false` and run the suite: every sell test still passes, because the branch is unreachable.","severity":"info","snippet":"        if (isSell && !(sender == address(this) && _isSelling()) && !court.tradesFreely()) revert SellRequiresPlea();","title":"beforeSwap's hook-is-sender branch never executes: v4-core skips hook callbacks when the hook itself swaps"}],"hash":"6148c38e46f8d57ba9ea4ea238542296c8fb9f4c5f7733a4343058d044ffc09b","nodeId":"64bb1da1-bd55-439f-b3e4-f24859795a7c","outcome":"completed","summary":"All five snippets match the tree exactly, and the only files written are `.imd-findings.json` and the scratch tests under `test/scratch/`.\n\n## Review complete: control flow of the PLEA hook, court and letterbox\n\n**Findings written to `.imd-findings.json`** (5 findings, 33 coverage rows covering all 28 listed entry points plus four invariants):\n\n1. **High. Plea deadlock fires the dead-man switch on an active Cabal.** `judge()` settles a plea only when one side reaches 17. If all 30 judges vote 16/14 or 15/15, the plea stays Pending forever: no judge can vote again, the seller cannot appeal, and the Cabal cannot issue any verdict that would reset the 33-hour clock. If it is the only plea in flight for 33 hours, the sell gate is removed permanently despite every judge having voted. The proof test fails at the first assertion on the current code.\n\n2. **Low. The ban only gates `submitPlea`.** A seller banned by an upheld objection can still `appeal` a denied plea into round 2 and get a sale window, and an already-open window keeps selling. Proof test attached.\n\n3. **Low. `sell` charges the whole amount to the window before the swap.** When in-range liquidity runs out, the hook refunds the unsold PLEA, but the court has already spent the window and recorded the sale at the full amount. Reproduced: 0.03 PLEA sold, 1e21 charged, window closed.\n\n4. **Low. One objection per sale lets the seller take the slot** on their own sale, shutting out other objectors. If the Cabal does not rule in 48 hours the seller reclaims the bond and the sale can never be objected to again.\n\n5. **Info. The hook's \"selling\" transient-flag branch in `beforeSwap` never runs.** v4-core skips hook callbacks when the hook itself is the swapper, so the gate for the hook's own sell rests on that exemption, not on the flag. Behaviour is correct; the security notes describe the wrong mechanism.\n\n**What held:** the sell gate for every other sender (direction from `zeroForOne`, identity never read from hookData, exact-in and exact-out both caught), the liquidity gate, dead-man latch ordering, the court's IMD accounting, the letterbox's signature and nonce handling, the token, and all owner functions.\n\n**Not reached:** whether the live IMD launch factory adds liquidity as the PoolManager's direct sender. If it goes through a position manager, `beforeAddLiquidity` would refuse the seed inside the launch transaction. That is recorded as an unreached coverage row.","treeHash":null,"usage":{"cachedInputTokens":1411511,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":54433,"runtime":"claude","turns":40,"wallClockMs":764172}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8ec0eeb0fd2a5504","findings":[{"citation":"resolved","description":"Merged: audit_flow ef3be99e, audit_permissions dc51e113, audit_economics d4f5b920 (same root cause). judge() settles a plea only when approveVotes or denyVotes reaches QUORUM (17). With 30 judges, any split from 14/16 to 16/14 leaves every judge marked hasScored, so further judge() reverts AlreadyVoted. The plea stays Pending: appeal() reverts WrongStatus(Pending), only the seller can withdraw (and a seller who wants free trading will not), pendingCount stays > 0, and no verdict can update lastVerdictAt. deadManDeadline() = max(pendingSince,lastVerdictAt)+33h therefore runs out unless another plea gets a verdict, and tradesFreely() becomes true, permanently removing the sell gate (PleaHook.beforeSwap line 148). An honest split on a borderline plea is enough. The brief's 'median score counts' is never applied once all 30 have voted. The only rescue is the owner swapping judges with setJudges inside 33h, which is not documented. Impact: the core sell restriction can break permanently, so high. Fix: once approveVotes+denyVotes == JUDGE_COUNT without a quorum, settle the plea (median against the bar, or deny so the appeal path opens) and call _leavePending.","line":317,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\n/// @dev Stands in for PleaHook: the court only needs `owner()` from it for administration.\ncontract HookStub {\n    address public owner;\n\n    constructor(address owner_) {\n        owner = owner_;\n    }\n}\n\n/// @notice A plea on which all 30 judges vote without 17 agreeing is stuck Pending forever: no judge can\n/// vote again, the seller cannot appeal, and the Cabal cannot produce any verdict that would reset the\n/// dead-man clock. 33 hours later the sell gate is gone for good although every judge did their job.\n/// Fails on the current code (the plea is still Pending after 30 votes); passes once the court settles\n/// a plea when the whole Cabal has voted.\ncontract DeadlockTest is Test {\n    PleaCourt court;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address[] judges;\n\n    function setUp() public {\n        HookStub hook = new HookStub(owner);\n        court = new PleaCourt(address(hook));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function test_splitCabalCannotReachAVerdictAndTheDeadManFires() public {\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"borderline\", 1e18);\n\n        // 16 approve (45 + 26 = 71), 14 deny (44 + 26 = 70): a majority of neither side.\n        for (uint256 i = 0; i < 16; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 45, 26);\n        }\n        for (uint256 i = 16; i < 30; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 44, 26);\n        }\n\n        // All 30 judges voted: the plea must have a verdict and must no longer count as pending.\n        assertTrue(\n            court.pleaStatus(id) != IPleaCourt.Status.Pending,\n            \"30 votes cast, 16 approve / 14 deny: the plea has no verdict and stays Pending\"\n        );\n        assertEq(court.pendingCount(), 0, \"an undecidable plea keeps the dead-man clock running\");\n        assertEq(court.deadManDeadline(), 0, \"no plea is pending, so no deadline should run\");\n\n        // Nothing the Cabal can do resets the clock while this is the only plea; 33h later the gate is gone.\n        vm.warp(block.timestamp + 33 hours);\n        assertFalse(court.tradesFreely(), \"the sell gate was lost although the whole Cabal voted\");\n    }\n}","reproduction":"Reproduced with test/scratch/Proof_ef3be99e3569.t.sol (attached): setJudges(30); seller submitPlea('borderline',1e18); judges[0..15] judge(id,45,26) (approve, 71>=71); judges[16..29] judge(id,44,26) (deny, 70). Expected: a verdict after 30 votes, pendingCount 0, deadManDeadline 0, and tradesFreely() false 33h later. Actual: status Pending (16/14), pendingCount 1, any further vote reverts AlreadyVoted, and after vm.warp(+33h) tradesFreely()==true. The test fails with '30 votes cast, 16 approve / 14 deny: the plea has no verdict and stays Pending'. The two other specialist proofs (Proof_dc51e113aac5, Proof_d4f5b920d1f4) also fail on this code for the same reason.","severity":"high","snippet":"        if (p.approveVotes >= QUORUM) _decide(pleaId, p, true);","title":"Split Cabal (no side reaches 17 of 30) leaves the plea Pending forever and lets the dead-man switch fire on a Cabal that voted in full"},{"citation":"resolved","description":"From audit_permissions 4e1a996b. The dead-man clock is reset only by a verdict (_decide sets lastVerdictAt) or when pendingCount drops to 0 (_leavePending clears pendingSince). withdrawPlea accepts a Pending plea at any vote count and throws its votes away without recording a verdict. A few wallets keep overlapping pleas (_submit checks no PLEA balance and needs only gas). Each wallet submits before the previous one withdraws, so pendingCount never reaches 0, pendingSince stays at the first plea's time and lastVerdictAt never moves. Each plea is withdrawn before its 17th vote. The Cabal keeps voting, yet 33h after the first plea tradesFreely() is true and the sell gate is gone for good. SECURITY.md line 75 ('A Cabal that judges at all is never tripped by spam') does not hold. The attacker pays only gas. The attack needs no verdict to land on anyone else's plea in those 33h, which is likely in a quiet launch period. The same withdrawal also lets a seller dodge a plea heading for denial. Fix: refuse withdrawal of a Pending plea once its current round has votes, or let judging activity or withdrawals reset the timer.","line":363,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\n\n/// @dev Stands in for PleaHook: the court only reads `owner()` from it.\ncontract HookStub {\n    address public owner;\n\n    constructor(address owner_) {\n        owner = owner_;\n    }\n}\n\n/// @notice Sellers who withdraw their pleas while the Cabal is voting stop every verdict from landing.\n/// With overlapping pleas pendingCount never reaches zero, pendingSince never moves, and 33 hours after the\n/// first plea the sell gate is gone forever although the Cabal cast 16 votes on every plea (one every 5 hours).\n/// Fails on the current code; passes once a mid-vote withdrawal can no longer starve the Cabal of verdicts\n/// (withdrawal refused once votes are cast, or judging activity / withdrawal resets the clock).\ncontract WithdrawDeadManTest is Test {\n    PleaCourt court;\n    address owner = makeAddr(\"owner\");\n    address[] judges;\n    address[] wallets;\n\n    function setUp() public {\n        HookStub hook = new HookStub(owner);\n        court = new PleaCourt(address(hook));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        for (uint256 i = 0; i < 8; i++) {\n            wallets.push(makeAddr(string.concat(\"wallet\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function test_withdrawBeforeQuorumTripsDeadManOnActiveCabal() public {\n        uint256 t0 = block.timestamp;\n        vm.prank(wallets[0]);\n        uint256 prev = court.submitPlea(\"case 0\", 1e18);\n\n        for (uint256 i = 1; i <= 6; i++) {\n            vm.warp(t0 + i * 5 hours);\n            // next attacker wallet keeps a plea pending so pendingSince never resets\n            vm.prank(wallets[i]);\n            uint256 next = court.submitPlea(\"case\", 1e18);\n            // the Cabal is alive: 16 approvals land on the previous plea\n            for (uint256 j = 0; j < 16; j++) {\n                vm.prank(judges[j]);\n                court.judge(prev, 45, 26);\n            }\n            // its seller withdraws just before the 17th vote (refusal is an acceptable fix)\n            vm.prank(wallets[i - 1]);\n            try court.withdrawPlea(prev) {} catch {}\n            vm.prank(judges[16]);\n            try court.judge(prev, 45, 26) {} catch {}\n            prev = next;\n        }\n\n        vm.warp(t0 + 33 hours);\n        assertFalse(court.tradesFreely(), \"Cabal voted throughout, yet withdrawals freed trading forever\");\n    }\n}","reproduction":"test/scratch/WithdrawDeadMan.t.sol (attached): setJudges(30). At t0 wallet0 submitPlea. For i=1..6 at t0+5h*i: wallet_i submitPlea (pendingCount stays >= 1); judges[0..15] judge(prev,45,26); wallet_{i-1} withdrawPlea(prev); the 17th judge(prev) reverts WrongStatus(Withdrawn). That is 96 votes over 30 hours, with lastVerdictAt 0 and pendingSince t0. vm.warp(t0+33h). Expected: tradesFreely()==false because the Cabal is demonstrably active. Actual: true. The test fails with 'Cabal voted throughout, yet withdrawals freed trading forever'.","severity":"high","snippet":"        if (p.status == Status.Pending) _leavePending();","title":"Sellers can withdraw pleas mid-vote so no verdict ever lands: overlapping pleas trip the permanent dead-man switch while the Cabal is actively judging"},{"citation":"resolved","description":"Merged: audit_math b5a79a5b, audit_permissions d9bbb470, audit_economics 91ab7b37. _strike zeroes seasonPoints[season] and the _board entry of the season current when the ruling lands, while Sale records no season. endSeason() is permissionless and can be called the moment seasonStart+30d passes. ruleObjection can take up to 48h or more. So a sale near the season's end, or a plea approved after the season has run its 30 days but before endSeason is called, gets its points credited as claimable IMD before the upheld ruling arrives. claimable is never reduced, so claim() pays it out. The strike instead zeroes whatever the seller earned in the new season. Other winners lose the pot share diluted by points the Cabal voided. This breaks a stated guarantee under a timing condition the seller can force, so medium. Fix: store the season on the Sale and strike that season, and hold back or claw back the prize credited to a seller with an open objection.","line":525,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {PLEAToken} from \"src/PLEAToken.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Stands in for the launch factory: opens the pool and seeds full-range liquidity.\ncontract ScratchFactory is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function initialize(PoolKey memory key, uint160 price) external {\n        manager.initialize(key, price);\n    }\n\n    function addLiquidity(PoolKey memory key, int256 liquidity) external {\n        manager.unlock(abi.encode(key, liquidity));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (PoolKey memory key, int256 liquidity) = abi.decode(data, (PoolKey, int256));\n        (BalanceDelta delta,) = manager.modifyLiquidity(\n            key,\n            ModifyLiquidityParams({\n                tickLower: TickMath.minUsableTick(key.tickSpacing),\n                tickUpper: TickMath.maxUsableTick(key.tickSpacing),\n                liquidityDelta: liquidity,\n                salt: 0\n            }),\n            \"\"\n        );\n        _pay(key.currency0, delta.amount0());\n        _pay(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _pay(Currency c, int128 amount) private {\n        if (amount < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        }\n    }\n}\n\n/// @notice An upheld objection must void the seller's season points (README: \"their season points are\n/// voided\"). When the season has ended between the verdict and the ruling, `endSeason` has already turned\n/// those points into claimable IMD and `_strike` only zeroes the *next* season, so the struck seller keeps\n/// the prize. The seller can force this: `endSeason` is permissionless.\ncontract StrikeAfterSeasonEndTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 internal constant FLAGS = (1 << 13) | (1 << 11) | (1 << 7);\n\n    address internal owner = makeAddr(\"owner\");\n    address internal seller = makeAddr(\"seller\");\n    address internal objector = makeAddr(\"objector\");\n\n    PoolManager internal manager;\n    ScratchIMD internal imd;\n    ScratchFactory internal factory;\n    PLEAToken internal token;\n    PleaHook internal hook;\n    PleaCourt internal court;\n    PoolKey internal key;\n    address[] internal judges;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        imd = new ScratchIMD();\n        factory = new ScratchFactory(manager);\n        token = new PLEAToken();\n\n        bytes memory initCode =\n            abi.encodePacked(type(PleaHook).creationCode, abi.encode(manager, address(token), owner, address(factory)));\n        bytes32 salt = _mine(initCode);\n        hook = new PleaHook{salt: salt}(manager, address(token), owner, address(factory));\n        court = hook.court();\n\n        (address c0, address c1) =\n            address(token) < address(imd) ? (address(token), address(imd)) : (address(imd), address(token));\n        key = PoolKey({\n            currency0: Currency.wrap(c0),\n            currency1: Currency.wrap(c1),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        factory.initialize(key, SQRT_PRICE_1_1);\n        token.transfer(address(factory), 1e24);\n        imd.transfer(address(factory), 1e24);\n        factory.addLiquidity(key, 1e21);\n\n        token.transfer(seller, 1e22);\n        imd.transfer(objector, 10e18);\n\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n\n        // The prize pot: 100 IMD held by the court that is nobody's yet.\n        imd.transfer(address(court), 100e18);\n    }\n\n    function test_upheldObjectionDoesNotVoidPrizeAfterSeasonEnd() public {\n        // The season is over but nobody has called endSeason yet.\n        vm.warp(court.seasonStart() + court.SEASON_LENGTH());\n\n        vm.prank(seller);\n        uint256 pleaId = court.submitPlea(\"I only sell to pay my rent. Fact: rent is due.\", 1e21);\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.judge(pleaId, 45, 26);\n        }\n        assertEq(court.seasonPoints(1, seller), 45, \"median 45 awarded to season 1\");\n\n        // The seller closes the season themselves: the points become claimable IMD at once.\n        vm.prank(seller);\n        try court.endSeason() {} catch {}\n\n        // The seller sells inside the 7-minute window, and the sale is objected to and upheld by 17 judges.\n        vm.startPrank(seller);\n        token.approve(address(hook), 1e21);\n        (uint256 saleId,) = hook.sell(1e21, 0);\n        vm.stopPrank();\n\n        vm.startPrank(objector);\n        imd.approve(address(court), court.OBJECTION_BOND());\n        uint256 objectionId = court.object(saleId);\n        vm.stopPrank();\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.ruleObjection(objectionId, true);\n        }\n        assertGt(court.bannedUntil(seller), block.timestamp, \"seller is banned\");\n\n        // Give any fix that defers the season end a chance to settle it now.\n        vm.warp(block.timestamp + 3 days);\n        try court.endSeason() {} catch {}\n\n        // Expected: a seller whose sale was struck gets no season prize. Actual: 100 IMD is claimable.\n        assertEq(court.claimable(seller), 0, \"struck seller keeps the season-1 prize\");\n    }\n\n    function _mine(bytes memory initCode) internal view returns (bytes32) {\n        bytes32 h = keccak256(initCode);\n        for (uint256 i = 0; i < 1_000_000; i++) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if ((uint160(a) & ((1 << 14) - 1)) == FLAGS) return bytes32(i);\n        }\n        revert(\"no salt\");\n    }\n}","reproduction":"test/scratch/Proof_b5a79a5bb394.t.sol (attached, from audit_math): court holds 100 IMD of pot; block.timestamp >= seasonStart+30d. seller submitPlea(text,1e21), 17 judges judge(id,45,26), so seasonPoints[1][seller]=45. seller calls endSeason(): claimable[seller]=100e18. seller hook.sell(1e21,0) gives saleId 1. objector object(1); 17 judges ruleObjection(1,true). Expected: claimable[seller]==0. Actual: 100e18. The test fails with 'struck seller keeps the season-1 prize: 100000000000000000000 != 0'.","severity":"medium","snippet":"        seasonPoints[season][account] = 0;","title":"An objection upheld after endSeason strikes the new season, not the sale's: the struck seller keeps and claims the old season's prize, and honest new-season points are wiped"},{"citation":"resolved","description":"From audit_permissions d4c39e74. _submit never checks cabalConfigured(), and error CabalNotConfigured is declared but unused. The factory's launch cannot call setJudges (it is owner-only), so every launch opens with no judges. A stranger's plea in the first block sets pendingSince. setJudges does not touch pendingSince or lastVerdictAt, so the owner must install 30 judges and get 17 matching votes, all within 33h of a plea it did not choose. README line 156 ('set the judges before the first plea or within 33 hours of it') is wrong: setting the judges within 33h does not stop the switch. The failure is permanent and the trigger costs only gas. Fix: revert CabalNotConfigured in _submit until the panel is set, or start the clock from setJudges.","line":273,"path":"src/PleaCourt.sol","reproduction":"Fresh court with no setJudges. stranger submitPlea('x',1) at T. warp T+32h; owner setJudges(30 addresses) as the README says. warp T+33h. Expected: still gated. Actual: tradesFreely()==true, and judge() now reverts FreeTrading (via _latchDeadMan), so the Cabal can never recover it.","severity":"medium","snippet":"        _latchDeadMan();","title":"Anyone can start the irreversible dead-man clock before the Cabal exists, and installing the panel does not reset it"},{"citation":"resolved","description":"Merged: audit_permissions 73d8518a, audit_economics 62b0459d. appeal() enforces one appeal (p.appealed) and APPEAL_DELAY. But withdrawPlea accepts a Denied plea and clears activePlea, and _submit keeps no record of earlier denials or any cooldown. A denied seller can open a fresh round-1 plea in the same block, as often as they like, until 17 judges approve. Each retry also costs the Cabal 17 more transactions. This breaks an explicit requirement of the brief. Fix: on denial record a per-seller earliest-next-plea time (decidedAt+APPEAL_DELAY) and a hearings counter that survive withdrawal, and check them in _submit.","line":364,"path":"src/PleaCourt.sol","reproduction":"setJudges(30). seller submitPlea('x',1e18)=id1; judges[0..16] judge(id1,10,0) makes it Denied at T. appeal(id1) reverts AppealTooEarly. Same block: withdrawPlea(id1) succeeds; submitPlea('x',1e18)=id2 succeeds (Pending); judges[0..16] judge(id2,45,26) makes it Approved and hook.sell(1e18,0) succeeds, all at timestamp T. Expected: no new hearing before T+4h and at most one re-hearing. Actual: unlimited immediate hearings.","severity":"medium","snippet":"        else if (p.status != Status.Denied) revert WrongStatus(p.status);","title":"Denied seller withdraws and resubmits at once: the 'one appeal after 4 hours' rule is bypassed with unlimited immediate re-hearings"},{"citation":"resolved","description":"Merged: audit_permissions e50d6f01, audit_flow 77005d08. bannedUntil is written in ruleObjection and read only in _submit. authorizeSale, appeal and judge/_decide never check it, and the uphold branch does not close the seller's active plea. So after 17 upholds: (a) the rest of an open 7-minute window still sells; (b) a plea already Pending can be approved, which calls _award and puts the seller's points back on the board right after _strike voided them; (c) a Denied plea can be appealed during the ban. The objection's only sanction, the ban, is partly void. Fix: check bannedUntil in authorizeSale, appeal and _decide, or close the seller's active plea in the uphold branch.","line":278,"path":"src/PleaCourt.sol","reproduction":"(a) Plea for 2e18 approved; hook.sell(1e18,0) gives saleId 1; stranger object(1); 17 judges ruleObjection(1,true) inside the window, so bannedUntil > now. Then hook.sell(1e18,0) succeeds (authorizeSale has no ban check). (c) seller's plea A approved and fully sold (saleId 1); plea B submitted and denied by 17 judge(B,10,0); object(1) upheld. submitPlea reverts Banned (correct), but after warp +4h appeal(B) succeeds and B is Pending in round 2. Expected: both refused during the ban. Actual: both succeed.","severity":"medium","snippet":"        if (bannedUntil[seller] > block.timestamp) revert Banned(bannedUntil[seller]);","title":"A ban is only checked in _submit: a banned seller keeps selling an open window, can appeal or get an in-flight plea approved, and earns points again right after the strike"},{"citation":"resolved","description":"From write_foundry_tests 3c084e50. _board keeps only the current top 25. _award evicts the lowest entry when the board is full, and the evicted account's points stay only in seasonPoints. _strike pops the struck seller and never promotes the best off-board account. With 26 positive sellers, striking one leaves 24 entries while 25 eligible sellers remain. endSeason pays only _board, so the 26th seller (now rank 25) gets nothing and the others split their share. This breaks 'top 25 split the pot', and a specific party loses funds, so medium. Fix: keep all season participants (or rebuild the board from seasonPoints at season end), or promote on strike.","line":529,"path":"src/PleaCourt.sol","reproduction":"setJudges(30). Approve 26 sellers with scores 20..45 (17 judges each, factPoints 71). The 20-point seller is evicted (rank 26). The 45-point seller sells (saleId 1); object(1); 17 ruleObjection(1,true). Expected: leaderboard().length==25 including the 20-point seller, who gets a prize at endSeason. Actual: length 24 and the 20-point seller's claimable stays 0 after endSeason with a funded pot.","severity":"medium","snippet":"                _board[i] = _board[n - 1];\n                _board.pop();","title":"An upheld objection removes a winner from the top-25 board without promoting the next eligible seller, so a prize winner is excluded"},{"citation":"resolved","description":"From audit_permissions 82ce6f3e. unlockCallback charges potFeeBps on every hook.sell, including the free branch (sell line 160-161 skips authorizeSale but not the fee). Router sells at that point pay nothing. After the switch, _submit and judge revert FreeTrading, so _award never runs again. The first endSeason empties the board, and every later endSeason sees totalPoints 0 and just rolls the pot. PleaCourt has no sweep or alternative destination, so fees from free sells (up to 10% of proceeds) and any pot left once the board is empty are permanently stuck. Fix: skip the fee when free is true, and give the post-switch pot a final payout or recipient.","line":188,"path":"src/PleaHook.sol","reproduction":"Owner setPotFeeBps(1000). setJudges; stranger submitPlea('x',1e18); warp 33h; triggerDeadManSwitch(). seller approves the hook and calls hook.sell(1e18,0): prizePot() rises by 10% of out. warp 30d, endSeason(); warp 30d, endSeason(). Expected: no fee on a free sale, or a way to distribute it. Actual: prizePot() unchanged after both season ends, no winners, and no function can move the IMD.","severity":"medium","snippet":"        uint256 fee = out * potFeeBps / BPS;","title":"After the dead-man switch, hook.sell still takes the pot fee, and that IMD is locked in the court forever because no one can earn points again"},{"citation":"resolved","description":"Merged: audit_math 2ddb5111, write_foundry_tests fe4d87f6, audit_flow ba5f4e36. sell calls court.authorizeSale(msg.sender, amountIn) before the swap, which adds amountIn to p.sold, stores Sale.amount=amountIn and closes the window when sold==amount. The swap is exact input with the extreme price limit, so a thin pool fills only part of it. Line 167 refunds the rest to the seller, but the court is never told. The seller's approved allowance is spent on PLEA they still hold, and the window may close, so a new plea and a new Cabal verdict are needed to sell the rest. The Sale that objectors judge also shows more than was sold, and SaleRecorded disagrees with SellExecuted. No tokens are lost and the seller is the main party harmed, so low. Fix: reserve before the swap and settle p.sold and Sale.amount to consumed afterwards (a second hook-only call), or revert when consumed < amountIn.","line":161,"path":"src/PleaHook.sol","reproduction":"Local PoolManager with a mined PleaHook; init at 1:1, fee 12500, spacing 60; liquidity 1e18 only in ticks [-60,60]. Plea for 1e18 approved by 17 judges; seller sell(1e18,1). Actual input consumed is about 3.04e15 and the rest is refunded. Expected: plea.sold == consumed and the window stays Approved for the rest. Actual: plea.sold == 1e18, status Closed, sale(1).amount == 1e18, saleWindow(seller) == (0,0).","severity":"low","snippet":"        if (!free) saleId = court.authorizeSale(msg.sender, amountIn);","title":"A partial fill charges the whole amountIn to the plea window and Sale record, although the unconsumed PLEA is refunded"},{"citation":"resolved","description":"Merged: audit_flow c7d5232a, audit_permissions 30e1cd3b. object() allows a single objection per sale (s.objected is never cleared), and the seller is not excluded. A seller can object to their own sale in the same transaction or block, so every honest objector gets AlreadyObjected. Once 48h pass, reclaimObjectionBond and ruleObjection are both live with no precedence, so the seller can resolve the objection by timeout before quorum, get the bond back, and make the sale permanently immune. Judges can still uphold within 48h, so low. Fix: refuse object() from s.seller, and allow one objection per objector, or ignore reclaim while a ruling is in progress.","line":390,"path":"src/PleaCourt.sol","reproduction":"Seller's plea approved; hook.sell(1e18,0) gives saleId 1; seller object(1) (1 IMD bond). stranger object(1) reverts AlreadyObjected. judges[0..15] ruleObjection(1,true); warp 48h; seller reclaimObjectionBond(1) succeeds; judges[16] ruleObjection(1,true) reverts ObjectionResolvedAlready. Expected: the stranger can object and the 17th vote bans. Actual: bannedUntil(seller)==0, points kept, bond returned.","severity":"low","snippet":"        if (s.objected) revert AlreadyObjected();","title":"One objection slot per sale goes to the first caller: the seller's own wallet can take it and, after 48h, reclaim it ahead of the 17th upholding vote"},{"citation":"resolved","description":"From audit_permissions 11bc12bf, downgraded. In beforeAddLiquidity, sender is the contract that called modifyLiquidity. If the owner allowlists a shared position manager, as the setLiquidityProvider NatSpec ('a position manager, say') and README step 4 suggest, any holder can place single-sided PLEA liquidity above the price, let buyers fill it, and remove it for IMD (no remove callback). That sells PLEA with no plea. Under the IMD factory model the factory adds liquidity directly, so this needs an owner configuration step. That is why it is low rather than medium. Fix: correct the docs to forbid shared routers, or gate on the end LP.","line":136,"path":"src/PleaHook.sol","reproduction":"Owner setLiquidityProvider(positionManager,true). A stranger with no plea adds PLEA-only liquidity at ticks above the current price through the manager; a buyer swaps IMD in across the range; the stranger removes it. Expected: the add is refused. Actual: the stranger receives IMD for PLEA with no plea and no beforeSwap check.","severity":"low","snippet":"        if (sender != factory && !liquidityProviders[sender] && !court.tradesFreely()) revert LiquidityRestricted();","title":"The liquidity allowlist checks the calling router, not the LP: allowlisting a shared position manager opens a plea-free sell route to every holder"},{"citation":"resolved","description":"From audit_permissions 02b947b1. _award runs in _decide at approval. The only way to void points is an objection to a Sale. _submit checks no balance, so a 1-wei plea from an empty wallet, if approved, earns full median points with no sale to object to. Judges must approve it, so low. Fix: award points in authorizeSale, or require a sale to stand past the objection window.","line":331,"path":"src/PleaCourt.sol","reproduction":"makeAddr('nobody') holds 0 PLEA; submitPlea('x',1); 17 judges judge(id,45,26): seasonPoints(1,nobody)=45 and saleCount stays 0. With a 100e18 pot, warp 30d and endSeason(): claimable(nobody)=100e18, even though nothing was sold or open to objection.","severity":"low","snippet":"            _award(p.seller, median);","title":"Season points come from approval alone: a wallet with no PLEA that never sells earns prize points that no objection can reach"},{"citation":"resolved","description":"From audit_permissions fef686be. Only postSigned increments nonces[seller], and there is no cancel function. A seller who signed for a relayer and then pleaded directly leaves the signature valid. Anyone can later post the old text and amount under the seller's address, taking their active-plea slot (the seller must withdraw) and starting a dead-man clock. No funds move, and the window is bounded by the signed deadline. Fix: add a seller-callable nonce increment.","line":45,"path":"src/Letterbox.sol","reproduction":"Signer signs Plea('old',1e18,nonce 0,deadline now+30d). Signer calls box.post('new',5e18); that plea is approved and closed; nonces(signer) is still 0. At +10d a stranger calls postSigned(signer,'old',1e18,deadline,sig): it succeeds and activePlea(signer) is the old plea, so box.post('today',1e18) reverts ActivePleaExists.","severity":"low","snippet":"        uint256 nonce = nonces[seller]++;","title":"A signed Letterbox plea cannot be cancelled: post() does not consume the nonce, so an abandoned signature can be posted until its deadline"},{"citation":"resolved","description":"From audit_permissions 1d721b0d. judge() checks isJudge and hasScored but not msg.sender != p.seller; ruleObjection does not exclude the seller or the objector. A judge casts one of the 17 votes on their own plea (and the points it earns). Low because one vote is not a quorum. Fix: add the comparisons.","line":307,"path":"src/PleaCourt.sol","reproduction":"setJudges with judges[0]. judges[0] submitPlea('x',1e18)=id then judge(id,45,71). Expected: revert. Actual: hasScored(id,1,judges[0])==true and approveVotes==1.","severity":"low","snippet":"        if (hasScored[pleaId][round][msg.sender]) revert AlreadyVoted();","title":"No recusal: a judge may score their own plea and rule on objections to their own sale"},{"citation":"resolved","description":"From audit_permissions 98204cd4. setJudges replaces the whole panel in one transaction with no delay, and votes already cast stay, so a round can exceed 30 votes. The owner can install keys it controls and approve any plea, or install a silent panel so the dead-man switch fires. setSeat is read live per vote. renounceOwnership before setJudges means no panel can ever be set. These are trust assumptions of the design, not bypasses, but the SECURITY.md claim is inaccurate.","line":234,"path":"src/PleaCourt.sol","reproduction":"Owner pleads; judges[0..15] deny (16). Owner setJudges(30 fresh addresses); 17 of them judge(id,45,71), so status is Approved and scores(id,1).length==33.","severity":"info","snippet":"    function setJudges(address[] calldata panel) external onlyOwner {","title":"Owner trust assumptions; SECURITY.md line 36 is wrong that the owner cannot end the gate early"},{"citation":"resolved","description":"From audit_math 5870b1ad. The upper-bound guard is correct but reuses ZeroAmount, so a frontend shows 'zero amount' for an amount that is too large.","line":275,"path":"src/PleaCourt.sol","reproduction":"submitPlea('t', 2**128) reverts ZeroAmount(); submitPlea('t', 2**128-1) succeeds.","severity":"info","snippet":"        if (amount == 0 || amount > type(uint128).max) revert ZeroAmount();","title":"A plea amount above uint128.max reverts with the misleading ZeroAmount error"},{"citation":"resolved","description":"From audit_flow 1e420ba4. lib/v4-core/src/libraries/Hooks.sol:253 returns before calling the hook when msg.sender == the hook, so the SELLING_SLOT transient flag and the `sender == address(this) && _isSelling()` clause never run. Behaviour is still correct, because the hook only swaps inside onlyPoolManager unlockCallback after authorizeSale. SECURITY.md's explanation of the flag is inaccurate.","line":148,"path":"src/PleaHook.sol","reproduction":"Replace _isSelling() with false and run test/PleaHook.t.sol: every sell test still passes, because the branch is unreachable.","severity":"info","snippet":"        if (isSell && !(sender == address(this) && _isSelling()) && !court.tradesFreely()) revert SellRequiresPlea();","title":"beforeSwap's hook-is-sender branch is dead code: v4-core skips hook callbacks for the hook's own swaps"}],"hash":"6a0d4e0ab4edd8f5d9b9d8eb015abaccd88cefed7c035d8e5d55627f9e943346","nodeId":"2e3806a9-8ced-42c3-9d6a-9eb9179b56b9","outcome":"completed","summary":"The review is written to `.imd-findings.json`: 17 findings after merging duplicates (2 high, 6 medium, 6 low, 3 info) and a coverage row for all 28 entry points. I changed no source files and deleted my scratch tests afterwards.\n\n**High**\n1. **A split vote locks a plea for good** (`PleaCourt.sol:317`). If all 30 judges vote and neither side reaches 17 (16/14, 15/15, 14/16), no one can vote again. The plea stays pending and the seller can't appeal. 33 hours later the dead-man switch frees trading forever. Three specialists reported this; I merged them. Their three proofs all fail on the current code, and I attached `Proof_ef3be99e3569`.\n2. **Withdrawing mid-vote starves the Cabal of verdicts** (`PleaCourt.sol:363`). A few wallets keep overlapping pleas and each withdraws just before its 17th vote. The clock never resets and the switch fires while the judges are actively voting. The attacker pays only gas. I wrote my own proof (96 votes over 30 hours, still freed at 33h); it fails on the current code.\n\n**Medium**\n3. **A strike that lands after a season ends misses that season's prize.** A seller can call `endSeason` and claim their prize before an upheld objection lands. The strike then wipes their points in the new season instead. Three specialists reported this; I merged them, and the attached proof fails on the current code.\n4. Anyone can start the dead-man clock before any judges are installed, and installing them doesn't reset it. The README's \"set judges within 33 hours\" advice therefore doesn't protect the launch.\n5. A denied seller can withdraw and resubmit straight away, which gets around \"one appeal after 4 hours\".\n6. The 7-day ban is only checked when submitting a plea. A banned seller can still sell an open window, appeal, or have a pending plea approved and earn points again.\n7. A strike shrinks the 25-place leaderboard to 24 without promoting the next seller, so a prize winner is left out.\n8. After the dead-man switch, `hook.sell` still takes the pot fee. That money is locked forever because no one can earn points any more.\n\n**Low and info:**\n- A partial fill charges the whole requested amount to the sale window and sale record; three reports, merged.\n- The seller can take the only objection slot on their own sale.\n- The liquidity allowlist checks the calling router, not the LP. I downgraded this to low because it only matters if the owner allowlists a shared position manager.\n- Points can be earned from an approved plea with no sale behind it, so no objection can reach them.\n- A signed Letterbox plea can't be cancelled.\n- Judges can vote on their own plea.\n- Info: the owner's ability to replace judges, which contradicts `SECURITY.md` line 36; a misleading `ZeroAmount` error; and a dead branch in `beforeSwap`.\n\nI reproduced every high and medium against the code, mostly by tracing it step by step. Only items 1, 2 and 3 have runnable proofs.\n\n**Deployment:** the hook is within the size limits (initcode 32,914 bytes, runtime 9,942 bytes). Its constructor arguments and permissions match `launch.json`, and it accepts the 12,500 fee.","treeHash":null,"usage":{"cachedInputTokens":982500,"inputTokens":26,"model":"claude-opus-5-5","outputTokens":21456,"runtime":"claude","turns":15,"wallClockMs":263982}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8af9903f4ad1eed0","findings":[{"citation":"resolved","description":"ruleObjection's uphold branch calls _strike(seller), which zeroes only seasonPoints[season][account] of the CURRENT season and removes the account from the current _board. The objection flow spans a season boundary easily: a sale can be objected to for 20 minutes and ruled on for up to 48 hours, while endSeason() is permissionless and callable by anyone, the seller included, the moment seasonStart + 30 days passes. endSeason converts the board's points into claimable IMD via _credit and starts season+1, so a strike ruled afterwards hits the empty new season and the already-credited prize stays claimable; claim() pays it out at once and nothing claws it back. The README guarantee '17 upholds ban the seller and void their season points' is therefore not enforced for any sale whose objection is ruled after a season end, and an unprivileged seller can force the ordering: plead once the season has run its 30 days, get approved (points land in the old season), call endSeason and claim, then sell inside the 7-minute window. The other leaderboard winners are the victims: their pro-rata share of the pot was diluted by points the Cabal later voided. Boundary x invariant seam: the invariant 'struck sellers hold no season points' is enforced in _strike's body, but the season boundary moves the value out of its reach first. A minimal fix that keeps the design: have endSeason refuse (or hold back the entries of) sellers with a sale still inside its objection window or with an unresolved objection, or make _strike claw back the claimable credited for the season the sale belonged to.","line":525,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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 {PLEAToken} from \"src/PLEAToken.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Stands in for the launch factory: opens the pool and seeds full-range liquidity.\ncontract ScratchFactory is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function initialize(PoolKey memory key, uint160 price) external {\n        manager.initialize(key, price);\n    }\n\n    function addLiquidity(PoolKey memory key, int256 liquidity) external {\n        manager.unlock(abi.encode(key, liquidity));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (PoolKey memory key, int256 liquidity) = abi.decode(data, (PoolKey, int256));\n        (BalanceDelta delta,) = manager.modifyLiquidity(\n            key,\n            ModifyLiquidityParams({\n                tickLower: TickMath.minUsableTick(key.tickSpacing),\n                tickUpper: TickMath.maxUsableTick(key.tickSpacing),\n                liquidityDelta: liquidity,\n                salt: 0\n            }),\n            \"\"\n        );\n        _pay(key.currency0, delta.amount0());\n        _pay(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _pay(Currency c, int128 amount) private {\n        if (amount < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        }\n    }\n}\n\n/// @notice An upheld objection must void the seller's season points (README: \"their season points are\n/// voided\"). When the season has ended between the verdict and the ruling, `endSeason` has already turned\n/// those points into claimable IMD and `_strike` only zeroes the *next* season, so the struck seller keeps\n/// the prize. The seller can force this: `endSeason` is permissionless.\ncontract StrikeAfterSeasonEndTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 internal constant FLAGS = (1 << 13) | (1 << 11) | (1 << 7);\n\n    address internal owner = makeAddr(\"owner\");\n    address internal seller = makeAddr(\"seller\");\n    address internal objector = makeAddr(\"objector\");\n\n    PoolManager internal manager;\n    ScratchIMD internal imd;\n    ScratchFactory internal factory;\n    PLEAToken internal token;\n    PleaHook internal hook;\n    PleaCourt internal court;\n    PoolKey internal key;\n    address[] internal judges;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        imd = new ScratchIMD();\n        factory = new ScratchFactory(manager);\n        token = new PLEAToken();\n\n        bytes memory initCode =\n            abi.encodePacked(type(PleaHook).creationCode, abi.encode(manager, address(token), owner, address(factory)));\n        bytes32 salt = _mine(initCode);\n        hook = new PleaHook{salt: salt}(manager, address(token), owner, address(factory));\n        court = hook.court();\n\n        (address c0, address c1) =\n            address(token) < address(imd) ? (address(token), address(imd)) : (address(imd), address(token));\n        key = PoolKey({\n            currency0: Currency.wrap(c0),\n            currency1: Currency.wrap(c1),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        factory.initialize(key, SQRT_PRICE_1_1);\n        token.transfer(address(factory), 1e24);\n        imd.transfer(address(factory), 1e24);\n        factory.addLiquidity(key, 1e21);\n\n        token.transfer(seller, 1e22);\n        imd.transfer(objector, 10e18);\n\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n\n        // The prize pot: 100 IMD held by the court that is nobody's yet.\n        imd.transfer(address(court), 100e18);\n    }\n\n    function test_upheldObjectionDoesNotVoidPrizeAfterSeasonEnd() public {\n        // The season is over but nobody has called endSeason yet.\n        vm.warp(court.seasonStart() + court.SEASON_LENGTH());\n\n        vm.prank(seller);\n        uint256 pleaId = court.submitPlea(\"I only sell to pay my rent. Fact: rent is due.\", 1e21);\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.judge(pleaId, 45, 26);\n        }\n        assertEq(court.seasonPoints(1, seller), 45, \"median 45 awarded to season 1\");\n\n        // The seller closes the season themselves: the points become claimable IMD at once.\n        vm.prank(seller);\n        try court.endSeason() {} catch {}\n\n        // The seller sells inside the 7-minute window, and the sale is objected to and upheld by 17 judges.\n        vm.startPrank(seller);\n        token.approve(address(hook), 1e21);\n        (uint256 saleId,) = hook.sell(1e21, 0);\n        vm.stopPrank();\n\n        vm.startPrank(objector);\n        imd.approve(address(court), court.OBJECTION_BOND());\n        uint256 objectionId = court.object(saleId);\n        vm.stopPrank();\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.ruleObjection(objectionId, true);\n        }\n        assertGt(court.bannedUntil(seller), block.timestamp, \"seller is banned\");\n\n        // Give any fix that defers the season end a chance to settle it now.\n        vm.warp(block.timestamp + 3 days);\n        try court.endSeason() {} catch {}\n\n        // Expected: a seller whose sale was struck gets no season prize. Actual: 100 IMD is claimable.\n        assertEq(court.claimable(seller), 0, \"struck seller keeps the season-1 prize\");\n    }\n\n    function _mine(bytes memory initCode) internal view returns (bytes32) {\n        bytes32 h = keccak256(initCode);\n        for (uint256 i = 0; i < 1_000_000; i++) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if ((uint160(a) & ((1 << 14) - 1)) == FLAGS) return bytes32(i);\n        }\n        revert(\"no salt\");\n    }\n}","reproduction":"State: pool bound, 30 judges set, court holds 100 IMD of unowned pot, block.timestamp >= seasonStart + SEASON_LENGTH with endSeason not yet called. 1) seller: submitPlea(text, 1e21); 17 judges: judge(pleaId, 45, 26) -> seasonPoints[1][seller] = 45, _board = [seller]. 2) seller (anyone works): endSeason() -> claimable[seller] = 100e18, season = 2. 3) seller: approve, hook.sell(1e21, 0) -> saleId 1, inside the 7-minute window. 4) objector: object(1) posting the 1 IMD bond; 17 judges: ruleObjection(1, true) -> seller banned, _strike zeroes seasonPoints[2][seller] which is already 0. Expected: the struck seller has no season prize (claimable[seller] == 0). Actual: claimable[seller] == 100e18 and claim() transfers it. test/scratch/StrikeAfterSeasonEnd.t.sol fails on this code with 'struck seller keeps the season-1 prize: 100000000000000000000 != 0'.","severity":"medium","snippet":"        seasonPoints[season][account] = 0;","title":"Upheld objection cannot void season points once endSeason has paid them: a struck seller keeps the prize across a season boundary"},{"citation":"resolved","description":"sell calls court.authorizeSale(msg.sender, amountIn) before the swap runs, and authorizeSale does p.sold += uint128(amount) and stores Sale.amount = amount (src/PleaCourt.sol:218-226) and closes the window when sold == amount. The swap in unlockCallback is exact input with sqrtPriceLimitX96 at the extreme, so it legitimately fills only partly when the pool has less IMD than the sale needs; the hook even handles this (consumed < amountIn triggers a refund at line 167) but never tells the court. A launch pool seeded with PLEA only, the IMD launch pattern, holds exactly the IMD buyers have paid in, so this state is reached in normal use rather than only when a pool 'runs dry'. Result: the seller's approved amount is consumed by tokens that came back to their wallet, the window closes as if fully sold, a fresh plea (and another Cabal verdict) is needed to sell the rest, and the Sale record that objectors judge shows amountIn rather than what was sold. Boundary x invariant seam: the invariant 'sold == PLEA actually sold through the hook' holds on full fills and silently breaks on the price-limit edge. The seller is the only loser, hence low. Fix: pass the consumed amount back to the court after the swap (for example authorizeSale reserves and a second hook-only call settles sold/Sale.amount to consumed), or cap the swap so a partial fill reverts unless the seller opted in.","line":161,"path":"src/PleaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PLEAToken} from \"src/PLEAToken.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract ScratchIMD2 is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Stands in for the launch factory (opens the pool, seeds PLEA-only liquidity) and for a buyer's router.\ncontract ScratchFactory2 is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function initialize(PoolKey memory key, uint160 price) external {\n        manager.initialize(key, price);\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lower, int24 upper, int256 liquidity) external {\n        manager.unlock(abi.encode(uint8(0), key, lower, upper, liquidity, false, uint256(0)));\n    }\n\n    function buy(PoolKey memory key, bool zeroForOne, uint256 amountIn) external {\n        manager.unlock(abi.encode(uint8(1), key, int24(0), int24(0), int256(0), zeroForOne, amountIn));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 kind, PoolKey memory key, int24 lower, int24 upper, int256 liquidity, bool zeroForOne, uint256 amountIn)\n        = abi.decode(data, (uint8, PoolKey, int24, int24, int256, bool, uint256));\n        BalanceDelta delta;\n        if (kind == 0) {\n            (delta,) = manager.modifyLiquidity(\n                key, ModifyLiquidityParams({tickLower: lower, tickUpper: upper, liquidityDelta: liquidity, salt: 0}), \"\"\n            );\n        } else {\n            delta = manager.swap(\n                key,\n                SwapParams({\n                    zeroForOne: zeroForOne,\n                    amountSpecified: -int256(amountIn),\n                    sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n                }),\n                \"\"\n            );\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n}\n\n/// @notice `PleaHook.sell` spends `amountIn` of the seller's approved window before the swap, but the swap is\n/// exact-input with no price limit and may fill only partly (a launch pool seeded with PLEA only holds just\n/// the IMD buyers paid in). The unsold PLEA is refunded, yet the court keeps `sold = amountIn`, so the\n/// seller's window is consumed by tokens that were never sold.\ncontract PartialFillWindowTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 internal constant FLAGS = (1 << 13) | (1 << 11) | (1 << 7);\n\n    address internal owner = makeAddr(\"owner\");\n    address internal seller = makeAddr(\"seller\");\n\n    PoolManager internal manager;\n    ScratchIMD2 internal imd;\n    ScratchFactory2 internal factory;\n    PLEAToken internal token;\n    PleaHook internal hook;\n    PleaCourt internal court;\n    PoolKey internal key;\n    address[] internal judges;\n    bool internal tokenIs0;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        imd = new ScratchIMD2();\n        factory = new ScratchFactory2(manager);\n        token = new PLEAToken();\n\n        bytes memory initCode =\n            abi.encodePacked(type(PleaHook).creationCode, abi.encode(manager, address(token), owner, address(factory)));\n        hook = new PleaHook{salt: _mine(initCode)}(manager, address(token), owner, address(factory));\n        court = hook.court();\n\n        tokenIs0 = address(token) < address(imd);\n        (address c0, address c1) = tokenIs0 ? (address(token), address(imd)) : (address(imd), address(token));\n        key = PoolKey({\n            currency0: Currency.wrap(c0),\n            currency1: Currency.wrap(c1),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        factory.initialize(key, SQRT_PRICE_1_1);\n\n        // Launch-style seeding: PLEA only, in the range just above the opening price.\n        token.transfer(address(factory), 1e26);\n        imd.transfer(address(factory), 1e24);\n        if (tokenIs0) factory.addLiquidity(key, 60, TickMath.maxUsableTick(60), 1e21);\n        else factory.addLiquidity(key, TickMath.minUsableTick(60), -60, 1e21);\n\n        // One buyer brings 100 IMD into the pool.\n        factory.buy(key, !tokenIs0, 100e18);\n\n        token.transfer(seller, 1e24);\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function test_partialFillChargesWholeWindow() public {\n        uint256 amountIn = 1e22; // 10,000 PLEA approved; the pool holds ~100 IMD to pay for it\n        vm.prank(seller);\n        uint256 pleaId = court.submitPlea(\"Fact: I need liquidity.\", amountIn);\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.judge(pleaId, 45, 26);\n        }\n\n        uint256 before = token.balanceOf(seller);\n        vm.startPrank(seller);\n        token.approve(address(hook), amountIn);\n        hook.sell(amountIn, 0);\n        vm.stopPrank();\n        uint256 consumed = before - token.balanceOf(seller);\n        assertLt(consumed, amountIn, \"the swap filled only partly; the rest was refunded\");\n\n        IPleaCourt.Plea memory p = court.plea(pleaId);\n        // Expected: the window is spent only by what was sold. Actual: sold == amountIn and the window is closed.\n        assertEq(uint256(p.sold), consumed, \"court charged the window for PLEA that was refunded\");\n    }\n\n    function _mine(bytes memory initCode) internal view returns (bytes32) {\n        bytes32 h = keccak256(initCode);\n        for (uint256 i = 0; i < 1_000_000; i++) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if ((uint160(a) & ((1 << 14) - 1)) == FLAGS) return bytes32(i);\n        }\n        revert(\"no salt\");\n    }\n}","reproduction":"State: pool at tick 0, PLEA-only liquidity 1e21 in [60, maxUsableTick] (or the mirrored range when PLEA is currency1), one buy of 100 IMD, 30 judges set. 1) seller: submitPlea(text, 1e22); 17 judges: judge(pleaId, 45, 26) -> window for 1e22 PLEA. 2) seller: approve, hook.sell(1e22, 0). The swap consumes about 9.05e19 PLEA (90.49 PLEA, all the IMD depth) and refunds about 9.9e21; sell returns normally. Expected: plea.sold == consumed (about 9.05e19) and the window stays open for the rest. Actual: plea.sold == 1e22, status Closed, saleWindow(seller) reports nothing left, Sale.amount == 1e22. test/scratch/PartialFillWindow.t.sol fails on this code with 'court charged the window for PLEA that was refunded: 10000000000000000000000 != 90492462441161589961'.","severity":"low","snippet":"        if (!free) saleId = court.authorizeSale(msg.sender, amountIn);","title":"sell spends the approved window by amountIn before the swap, so a partial fill charges the seller for PLEA that is refunded and records an overstated Sale.amount"},{"citation":"resolved","description":"The uint128 downcast at line 285 is guarded, which is correct, but the guard reuses ZeroAmount for the upper bound, so a frontend or integrator decoding the revert sees 'zero amount' for an amount that is too large. No funds impact; a dedicated error (AmountTooLarge) would make the boundary legible.","line":275,"path":"src/PleaCourt.sol","reproduction":"submitPlea(\"text\", 2**128) reverts with ZeroAmount() although the amount is non-zero; submitPlea(\"text\", 2**128 - 1) succeeds. Expected: a revert naming the real cause.","severity":"info","snippet":"        if (amount == 0 || amount > type(uint128).max) revert ZeroAmount();","title":"A plea amount above uint128.max is refused with the misleading ZeroAmount error"}],"hash":"780759532b72884df88be6e2d31807b7671fd406c28b824fc02c713565491e48","nodeId":"36e41b21-a3e7-48cc-8d7b-3f6a65af15c1","outcome":"completed","summary":"The review is complete. The findings are in `.imd-findings.json` with three findings and coverage rows for all 28 listed entry points plus one invariant.\n\n**Findings**\n\n- **Medium, PleaCourt `_strike`:** an upheld objection only clears the current season's points. Because `endSeason` is permissionless and objections can be ruled up to 48 hours after a sale, a seller can plead once the season's 30 days have passed, get approved, call `endSeason` and `claim`, then sell. The later strike hits the empty new season and the prize stays with the struck seller, diluting the other winners. The scratch test fails on this code with the seller holding the full 100 IMD pot.\n- **Low, PleaHook `sell`:** the court spends the approved window by `amountIn` before the swap, but the exact-input swap with an extreme price limit fills only partly on a PLEA-only seeded pool. The refund is handled, but the court records the full amount as sold, closes the window and overstates the sale that objectors judge. The scratch test shows 10,000 PLEA charged against roughly 90 PLEA actually sold.\n- **Info:** an amount above the uint128 maximum reverts with the misleading `ZeroAmount` error.\n\n**Coverage of the math area.** I checked every division, downcast, timestamp boundary and vote threshold against the Math Precision, Boundary and Numerical Gap guides. The seat multiplier's floor division gives the same vote as exact arithmetic, the pot fee floors at most 1 wei in the seller's favour, pro-rata prize shares sum to no more than the pot with dust rolling over, bond and claimable accounting keep `prizePot` from underflowing, and IMD on mainnet has 18 decimals, so the 1-IMD bond constant is correctly scaled. Both the main suite and the two scratch reproductions behave as reported: 66 existing tests pass and the two proofs fail for the stated reasons.","treeHash":null,"usage":{"cachedInputTokens":988873,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":39950,"runtime":"claude","turns":27,"wallClockMs":574750}},{"artifacts":[],"attempt":1,"bundleHash":"42f6990ab6e9f6c95228dc83273f6109644bd1e371f3a88b7f5cca26a01b3f93","device":"42a6bb6aa496cd6b","findings":[],"hash":"85b55c9a679dfe7d3ac0dbb7aa0ab0c16c6226b55276c0104f2267ea11c4d100","nodeId":"b82ba0cc-b1c6-4cba-b324-7e6afd61cb8f","outcome":"completed","summary":"The manifest already in the tree is correct and fully verified, so I left it unchanged.\n\n**What I checked**\n\n- **Constructor order** in `src/PleaHook.sol:80` is poolManager, token, owner, factory, matching the manifest's four placeholders `$poolManager`, `$token`, `$owner`, `$factory`.\n- **Permissions** returned by getHookPermissions are beforeInitialize, beforeAddLiquidity and beforeSwap only, matching the manifest's list and the 0x2880 address flags named in notes.\n- **Token** is PLEAToken with name PLEA, symbol PLEA, 18 decimals and no constructor arguments.\n- **Pool fields** are exactly the launch's: IMD at 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, fee 12500, tickSpacing 60, initialPrice 79228162514264337593543950336.\n- **Schema shape**: first key is `\"kind\": \"univ4_hook\"`, notes are one string of 3544 characters, under the 4000 cap.\n- **Notes accuracy**: every view function, owner setter and constant the notes cite (7-minute window, 4-hour appeal, 20-minute objections, 33-hour dead-man, 30-day seasons, 1 IMD bond, 10% max pot fee, 30 judges with 17 quorum, bar 71, 1.2x seat multiplier) exists in the source with those values.\n\n**Test results**\n\n| Suite | Result |\n|---|---|\n| Project tests, verifier fuzz seed | 92 passed, 0 failed |\n| Protected floor suite, run from test/scratch with the manifest's values as env | 13 passed, 0 failed, 1 skipped |\n\nThe skip is the custom-token suite, which only applies to launches that bring their own token, so it does not apply here. The floor suite confirmed the declared flags match the implementation and the mined address, the callbacks refuse non-PoolManager callers, the hook accepts initialization from the factory on the IMD pool at fee 12500 and spacing 60, the hook is not owned by the factory, and the factory's zero-liquidity fee collect passes.\n\nNo file outside test/scratch was changed, and the working tree is clean apart from that scratch directory, which is discarded before submission.","treeHash":"2eb4814107ba5fcd6c637c25b45e0b6ca8c6cc0f","usage":{"cachedInputTokens":406921,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":6509,"runtime":"claude","turns":11,"wallClockMs":123666}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"9506ecec995bdbf9","findings":[{"citation":"resolved","description":"judge() decides a plea only when one side reaches QUORUM (17). With 30 judges, any split of 14-16 approvals (e.g. 15/15 or 16/14) means every judge has voted, hasScored blocks re-voting, and nothing can ever decide the plea: no finalisation path exists and the median score that the brief says 'counts' is never compared against the bar. The plea stays Pending, so pendingCount > 0 and deadManDeadline() keeps running from max(pendingSince, lastVerdictAt). If no other plea gets a verdict for 33 hours, tradesFreely() becomes true and the hook's beforeSwap (PleaHook.sol:148) lets every sell through, forever. The seller who holds the deadlocked plea has every reason not to withdraw it. SECURITY.md says 'A Cabal that judges at all is never tripped', but here the Cabal voted in full and the switch still fires. The only rescue is the owner rotating judges with setJudges so fresh addresses can vote in the same round, and that has to happen within 33 hours.","line":317,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract OwnerStub {\n    address public owner;\n\n    constructor(address o) {\n        owner = o;\n    }\n}\n\ncontract Imd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\ncontract SplitCabalDeadlockTest is Test {\n    PleaCourt court;\n    OwnerStub hookStub;\n    Imd imd;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address objector = makeAddr(\"objector\");\n    address[] judges;\n\n    function setUp() public {\n        hookStub = new OwnerStub(owner);\n        vm.prank(address(hookStub));\n        court = new PleaCourt(address(hookStub));\n        imd = new Imd();\n        vm.prank(address(hookStub));\n        court.bind(address(imd));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n        imd.transfer(objector, 10 ether);\n    }\n\n    function _vote(uint256 id, uint256 from, uint256 to, uint8 s, uint8 f) internal {\n        for (uint256 i = from; i < to; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, s, f);\n        }\n    }\n\n    /// All 30 judges vote, 15 approve and 15 deny: no verdict can ever be reached, and 33h later the\n    /// sell gate is gone forever.\n    function test_splitCabalDeadlocksPleaAndFreesTrading() public {\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"borderline plea\", 1e18);\n        _vote(id, 0, 15, 45, 26); // 45 + 26 >= 71: approve\n        _vote(id, 15, 30, 10, 0); // deny\n        IPleaCourt.Plea memory p = court.plea(id);\n        assertEq(p.approveVotes + p.denyVotes, 30, \"every judge has voted\");\n        // The whole Cabal has spoken; the plea must have a verdict now.\n        assertTrue(court.pleaStatus(id) != IPleaCourt.Status.Pending, \"plea stuck Pending after all 30 votes\");\n        vm.warp(block.timestamp + 33 hours);\n        assertFalse(court.tradesFreely(), \"dead-man fired although the Cabal voted in full\");\n    }\n}","reproduction":"setJudges(30 judges). seller.submitPlea('borderline plea', 1e18) -> id 1. Judges 0-14 call judge(1, 45, 26) (approve: 45+26>=71); judges 15-29 call judge(1, 10, 0) (deny). State: approveVotes=15, denyVotes=15, status=Pending, and any further judge() reverts AlreadyVoted. With no other verdicts, warp 33h: court.tradesFreely() == true, so router sells pass PleaHook.beforeSwap and the gate is off permanently. Expected: once all 30 have voted, the plea gets a verdict (by the median against 71 - factPoints, or denied without quorum), lastVerdictAt is set and the dead-man clock stops. Actual: no verdict is possible and trading is freed.","severity":"high","snippet":"        if (p.approveVotes >= QUORUM) _decide(pleaId, p, true);\n        else if (p.denyVotes >= QUORUM) _decide(pleaId, p, false);","title":"A split Cabal vote (neither side reaches 17 of 30) leaves the plea Pending forever, which trips the dead-man switch and permanently ends the sell gate"},{"citation":"resolved","description":"The brief says: 'DENIED: one appeal is allowed after 4 hours.' withdrawPlea accepts a Denied plea, clears activePlea[seller], and _submit applies no cooldown and checks no earlier denial. A denied seller can therefore submit a fresh round-1 plea in the next block. Repeated as often as they like, this replaces the single delayed appeal with unlimited immediate retries until enough judges approve (each verdict is decided by the first 17 votes on one side, so retries are worth something). It also lets a seller withdraw a pending plea that is heading toward denial (e.g. 16 deny votes) and restart with a clean count. Each retry also loads the Cabal with work.","line":363,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract OwnerStub {\n    address public owner;\n\n    constructor(address o) {\n        owner = o;\n    }\n}\n\ncontract Imd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\ncontract WithdrawResubmitTest is Test {\n    PleaCourt court;\n    OwnerStub hookStub;\n    Imd imd;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address objector = makeAddr(\"objector\");\n    address[] judges;\n\n    function setUp() public {\n        hookStub = new OwnerStub(owner);\n        vm.prank(address(hookStub));\n        court = new PleaCourt(address(hookStub));\n        imd = new Imd();\n        vm.prank(address(hookStub));\n        court.bind(address(imd));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n        imd.transfer(objector, 10 ether);\n    }\n\n    function _vote(uint256 id, uint256 from, uint256 to, uint8 s, uint8 f) internal {\n        for (uint256 i = from; i < to; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, s, f);\n        }\n    }\n\n    /// A denied seller withdraws and re-pleads at once: no 4-hour wait, unlimited retries.\n    function test_withdrawResubmitSkipsAppealDelay() public {\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"plea\", 1e18);\n        _vote(id, 0, 17, 10, 0);\n        assertEq(uint8(court.pleaStatus(id)), uint8(IPleaCourt.Status.Denied));\n        vm.warp(block.timestamp + 1);\n        vm.prank(seller);\n        try court.withdrawPlea(id) {} catch {}\n        vm.prank(seller);\n        vm.expectRevert();\n        court.submitPlea(\"plea again\", 1e18); // must not be possible before decidedAt + 4h\n    }\n}","reproduction":"seller.submitPlea('plea', 1e18) -> id 1; judges 0-16 call judge(1, 10, 0) -> Denied at time T. At T+1: seller.withdrawPlea(1) succeeds, then seller.submitPlea('plea again', 1e18) succeeds and returns id 2 in Pending. Expected: no new plea from this seller before T + 4h, and at most one re-judging (the appeal). Actual: an immediate new plea, repeatable without limit.","severity":"medium","snippet":"        if (p.status == Status.Pending) _leavePending();\n        else if (p.status != Status.Denied) revert WrongStatus(p.status);\n        p.status = Status.Withdrawn;\n        activePlea[p.seller] = 0;","title":"A denied seller withdraws and re-pleads immediately, which gets around the 'one appeal after 4 hours' rule and allows unlimited retries"},{"citation":"resolved","description":"_strike voids seasonPoints[season] and the board entry of the current season only. A sale made in the final 20 minutes of a season can be objected to, but the ruling needs 17 judges and may take up to 48h, while endSeason() can be called by anyone, the seller included, the moment the season ends. endSeason credits the prize from the season's points into claimable[seller]. The later upheld ruling bans the seller and zeroes their (empty) points for the new season, but leaves the credited prize untouched. The seller claims a prize paid for by objectors' bonds and pot fees, against the README's 'their season points are voided'.","line":525,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract OwnerStub {\n    address public owner;\n\n    constructor(address o) {\n        owner = o;\n    }\n}\n\ncontract Imd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\ncontract SeasonBoundaryStrikeTest is Test {\n    PleaCourt court;\n    OwnerStub hookStub;\n    Imd imd;\n    address owner = makeAddr(\"owner\");\n    address seller = makeAddr(\"seller\");\n    address objector = makeAddr(\"objector\");\n    address[] judges;\n\n    function setUp() public {\n        hookStub = new OwnerStub(owner);\n        vm.prank(address(hookStub));\n        court = new PleaCourt(address(hookStub));\n        imd = new Imd();\n        vm.prank(address(hookStub));\n        court.bind(address(imd));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n        imd.transfer(objector, 10 ether);\n    }\n\n    function _vote(uint256 id, uint256 from, uint256 to, uint8 s, uint8 f) internal {\n        for (uint256 i = from; i < to; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, s, f);\n        }\n    }\n\n    /// An objection upheld after endSeason voids nothing: the struck seller still claims last season's prize.\n    function test_upheldObjectionAfterSeasonEndKeepsPrize() public {\n        imd.transfer(address(court), 100 ether); // prize pot\n        address rival = makeAddr(\"rival\");\n        vm.prank(rival);\n        uint256 r = court.submitPlea(\"rival plea\", 1e18);\n        _vote(r, 0, 17, 45, 26);\n\n        vm.warp(block.timestamp + 30 days - 10 minutes);\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"plea\", 1e18);\n        _vote(id, 0, 17, 45, 26);\n        vm.prank(address(hookStub));\n        uint256 saleId = court.authorizeSale(seller, 1e18);\n\n        vm.startPrank(objector);\n        imd.approve(address(court), 1 ether);\n        uint256 oid = court.object(saleId);\n        vm.stopPrank();\n\n        vm.warp(block.timestamp + 10 minutes);\n        court.endSeason(); // anyone, the seller included, may call it first\n        for (uint256 i = 0; i < 17; i++) {\n            vm.prank(judges[i]);\n            court.ruleObjection(oid, true);\n        }\n        assertGt(court.bannedUntil(seller), block.timestamp, \"seller banned\");\n        assertEq(court.claimable(seller), 0, \"struck seller still holds the season's prize\");\n    }\n}","reproduction":"Pot = 100 IMD. Rival approved (median 45). At seasonStart+30d-10min: seller's plea approved (median 45 -> 45 points); hook.sell -> saleId 1. Objector calls object(1) with a 1 IMD bond. At seasonStart+30d: anyone calls endSeason() -> claimable[seller] = 50 IMD. 17 judges ruleObjection(1, true) -> seller banned. Expected: the seller's prize for the season of the struck sale is voided (or endSeason waits on open objections against that season's sales). Actual: claimable[seller] == 50e18, and claim() pays it.","severity":"medium","snippet":"        seasonPoints[season][account] = 0;","title":"An objection upheld after endSeason strikes the new season's points, so the banned seller keeps and claims the prize from the season their sale was in"}],"hash":"88658584f1e568592586fc2181733ea2138ffee1702bcc61f7f903a5e22282f2","nodeId":"b441330e-f6c2-4b2f-a999-e35755e414a9","outcome":"completed","summary":"I found three economic defects in the PLEA contracts, and each one has a Foundry proof test that fails on the current code for the stated reason. They're written to `.imd-findings.json` with a coverage row for all 28 entry points, plus two invariant rows. No repository files were changed; I deleted the scratch tests afterwards because their source is in the findings file.\n\n**Findings**\n\n1. **High: a split Cabal vote can permanently end the sell gate** (`src/PleaCourt.sol:317`). A plea only gets a verdict when 17 judges land on one side. If all 30 vote and neither side reaches 17 (15/15, 16/14), the plea stays Pending forever. Judges can't vote again and nothing else can close it. If no other plea gets a verdict for 33 hours, the dead-man switch fires and every sell goes through from then on. The seller holding that plea has every reason not to withdraw it. The only rescue is the owner swapping in new judge addresses within those 33 hours. This contradicts SECURITY.md's claim that \"a Cabal that judges at all is never tripped\".\n2. **Medium: denied sellers get around the \"one appeal after 4 hours\" rule** (`src/PleaCourt.sol:363`). A seller can withdraw a denied plea and submit a new one in the next block, as often as they like. They can also withdraw a plea that is heading toward denial and start over with no votes against them.\n3. **Medium: an upheld objection doesn't cancel the prize from the season of the sale** (`src/PleaCourt.sol:525`). Suppose a sale happens in a season's last 20 minutes and someone objects to it. Anyone, including the seller, can call `endSeason()` before the judges rule, which credits the seller's prize. The later ruling bans the seller but only clears points in the new season. In the test the banned seller still had 50 IMD to claim.\n\n**Coverage**\n- I checked the dead-man timing and its \"can't be undone\" lock, the plea/appeal/withdraw flow, objection bonds, the prize pot accounting, season payouts, leaderboard eviction, the seat multiplier and fact-point threshold, and the sell route's money flows. Apart from the three findings, these behave as intended.\n- I traced the sell-gate bypass paths:\n  - the swap direction check;\n  - the \"only the hook itself, during its own sell\" condition;\n  - the liquidity allowlist.\n\n  None can be bypassed without the owner.\n\n**Not reported**\n- **Already documented by the author:** selling on other venues, the Cabal having to be set within 33 hours of the first plea, and the owner replacing judges mid-vote.\n- **Too minor or unproven:** in `sell`, PLEA that isn't swapped is refunded but still uses up the seller's approved amount. Separately, an approved seller can sell tokens that other wallets send them. I didn't establish either as a material defect.\n\nThe math, permissions and control-flow areas belong to the other reviewers, and I didn't look into them beyond what touches the economics.","treeHash":null,"usage":{"cachedInputTokens":854661,"inputTokens":24,"model":"claude-opus-5-5","outputTokens":23655,"runtime":"claude","turns":15,"wallClockMs":260409}},{"artifacts":[],"attempt":1,"bundleHash":"3604722681ea99707040e01fecb48e1aa645dc0d98dd9f0e84b269070bfda9bb","device":"5835e48821d8827d","findings":[],"hash":"958490d37837dacbadbc461753b700c442e12c7c042bdeb8ef769cc9bf4d4375","nodeId":"b82ba0cc-b1c6-4cba-b324-7e6afd61cb8f","outcome":"completed","summary":"launch.json is written and verified. Only that file is new in the working tree.\n\n**What the manifest describes**\n\n- **Hook:** `PleaHook`, constructor arguments in declaration order: `$poolManager`, `$token`, `$owner`, `$factory`. The hook is Ownable with the owner placeholder as initial owner.\n- **Permissions:** beforeInitialize, beforeAddLiquidity, beforeSwap. These are exactly the three flags getHookPermissions returns in the source. No return-delta or remove-liquidity flags are declared.\n- **Token:** `PLEAToken`, name PLEA, symbol PLEA, 18 decimals, no constructor arguments.\n- **Pool:** paired currency IMD at the required lowercase address, fee 12500, tick spacing 60, initial price as the launch facts give it.\n- **Notes:** 3384 characters, under the 4000 cap. They explain the sell gate, the factory-only initialize callback, the pot fee in IMD (zero until the owner sets it), the court and letterbox the hook creates, the owner duties after launch, and the open items from SECURITY.md.\n\n**Checks run**\n\n- The build succeeds and all 66 tests pass with the verifier's fuzz seed.\n- The manifest parses as JSON with `kind` as its first key, string addresses in lowercase, numbers for fee, tickSpacing and decimals.\n- Git status shows only the new launch.json. No configuration, lib or test file was touched.\n\n**One thing to note for the requester:** the hook needs the 30 Cabal judge addresses set by the owner after launch. They are not constructor inputs and no placeholder covers them, so the manifest says so in notes rather than guessing. The hook accepts the listed LP fee, so no fee workaround was needed.","treeHash":"58b9da63dfc86cfb23a6b4c5854f1d3c2cbb8738","usage":{"cachedInputTokens":232067,"inputTokens":162,"model":"claude-fable-5-1","outputTokens":4426,"runtime":"claude","turns":11,"wallClockMs":68565}},{"artifacts":[],"attempt":1,"bundleHash":"def42cc151f0bc02cc0a6604f1875508b8a94700c7618ba44db419ef647c1864","device":"7f1dec5ffcbde1d8","findings":[],"hash":"96971422ca25f21d757bdf8f66f1b72fc3bbced9421d4f9fe7552cec2ed7d754","nodeId":"a87c4e27-252b-4c94-9512-a26f365dbc33","outcome":"completed","summary":"Both findings reproduced on the starting tree and both causes are fixed. The two docs are updated and every finding is answered in the responses file.\n\n**Finding d38c1c69, judge's own plea hangs (medium):** reproduced, fixed, proof disputed.\n- Cause: the seller is recused from their own plea, so a judge's plea can collect at most 29 votes, while the hung-Cabal rule waited for 30.\n- Fix in `src/PleaCourt.sol`: the hung-Cabal rule now settles once the vote count reaches a new public view that returns 29 when the seller sits on the Cabal and 30 otherwise.\n- The reported scenario now ends with a denial on the 29th vote, an empty pending count, and the sell gate intact 33 hours later. The seller can appeal or withdraw like any other denial.\n- The reviewer's proof still fails after the fix, so I answered it \"disputed\" rather than \"fixed\". After asserting the 29 votes it also expects a re-vote to revert with AlreadyVoted and withdrawal to revert with VotesCast. Both describe the hung state itself. Once the verdict lands the plea is Denied, so a re-vote reverts with WrongStatus, and withdrawing a denied plea is the accepted design from the previous round, which the replacement-plea re-hearing tests depend on. The reviewer's alternative fix, refusing pleas from judges, would fail the proof at submission. No fix can pass that proof as written. The dispute spells this out, including the exact error the proof produces now.\n\n**Finding 5ab39671, endSeason does not latch (low):** reproduced and fixed. The season end now latches a due dead-man switch before choosing between rolling the pot and sweeping it to the owner, so the sweep happens in that same call.\n\n**Tests added** in `test/PleaCourtRules.t.sol`: a judge-seller plea settling at 29 votes in both rounds, an outsider's plea still waiting for the 30th vote, and a season end that latches and sweeps. The existing rollover test still passes.\n\n**Checks:** forge build, forge fmt --check, and the full suite with the verifier's fuzz seed all pass.\n\n| Check | Result |\n| --- | --- |\n| forge build | exit 0 |\n| forge fmt --check | exit 0 |\n| forge test (seeded) | 92 passed, 0 failed |\n\nThe scratch copy of the reviewer's proof is the only failing test, and it lives under test/scratch, which is discarded. Nothing was committed, as the task did not ask for a commit.","treeHash":"f0e7da62dcff7dbf78c34de4bd123dcaff4212a2","usage":{"cachedInputTokens":1329181,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":19107,"runtime":"claude","turns":31,"wallClockMs":348236}},{"artifacts":[],"attempt":1,"bundleHash":"17e5d82efb38ff58852a0ce88861936143254210e48b159a33b814e877c675f3","device":"1f9cb1750ebf47ed","findings":[],"hash":"9c7a576f9769d697670cbbb2c6f9aa47353403c2baedb6a2634b203e49ab7adf","nodeId":"a87c4e27-252b-4c94-9512-a26f365dbc33","outcome":"completed","summary":"Done. The PLEA launch is implemented, tested and committed as `d533ed2`.\n\n**What was built** (all under `src/`, Foundry project at the repo root, solc 0.8.26 pinned, `bytecode_hash = \"none\"`):\n\n- `PLEAToken`: standard fixed-supply ERC-20, name and symbol `PLEA`, 10^27 units minted to the deployer, no admin functions.\n- `PleaHook`: constructor `($poolManager, $token, $owner, $factory)`. Flags `beforeInitialize | beforeAddLiquidity | beforeSwap` (`0x2880`). Buys always pass from any router. Sells pass only through the hook's own `sell`, which spends an approved plea's window and sends IMD straight to the seller, with an owner-set pot fee (0 until set, max 10%) to the prize pot in IMD. Liquidity adds are limited to the factory and allowlisted providers so single-sided LP cannot bypass the gate. No remove-liquidity callbacks, so the factory's fee collect is untouched.\n- `PleaCourt` (created by the hook's constructor): pleas with on-chain text, the 30-judge Cabal with 17-vote majority settling the median, per-judge fact points lowering the 71 bar, 1.2x seats, 7-minute windows, one appeal after 4 hours, bonded objections within 20 minutes, 30-day seasons with a top-25 leaderboard splitting the pot pro rata, and the 33-hour dead-man switch, which is latched permanently on the first state change after it is due.\n- `Letterbox` (created by the court): direct posting and EIP-712 signed posting by relayers.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| `forge build`, `forge fmt --check` | pass |\n| `forge test` with the verifier's fuzz seed | 66 passed, 0 failed |\n| Pinned floor tests run against the real creation code with the verifier's env shape | 16 passed (hook, owners, token, own-token) |\n| Runtime sizes | hook 9.9 KB, court 17.2 KB, all under EIP-170 |\n\n**Decisions to flag**\n\n- The brief names no fee rate, so the pot fee is an owner-settable value starting at zero rather than a guess. It stays in IMD per the brief, not converted to ETH.\n- via-IR is off: it folds `block.timestamp` across `vm.warp` calls and broke timing tests.\n- The judge addresses, seat holders, pot fee and any position-manager allowlist are listed in the README under \"After launch\". Judges must be set before the first plea or within 33 hours of it, or the dead-man switch frees trading.\n- `SECURITY.md` holds the adversarial review. Open items: an independent second reviewer, a timelock on the owner, and a fork rehearsal against mainnet IMD.","treeHash":"0f32c06bf7a35c0742e15e60f56c0dea2a0e93be","usage":{"cachedInputTokens":4851656,"inputTokens":1058,"model":"claude-fable-5-1","outputTokens":108468,"runtime":"claude","turns":59,"wallClockMs":1548630}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"66ceb1fcb4b0b455","findings":[{"citation":"resolved","description":"Asymmetry (approve/deny branches against the third state). judge() decides only when one side reaches 17. With 30 judges, any split from 14/16 to 16/14 leaves neither side at 17, and hasScored stops anyone voting again, so the brief's 'median score counts' never resolves it. There is no decision path for 'all 30 voted', and no judge, owner or anyone else can close the plea: withdrawPlea is seller-only (line 361). The plea therefore holds pendingCount above 0 for as long as the seller chooses, and a seller who wants free trading simply leaves it. While pendingCount > 0, deadManDeadline() always returns a deadline, so the 'no timer while nothing is pending' protection is gone permanently: any later 33-hour gap between verdicts, even with no other plea in the queue, makes tradesFreely() true for good. An honest disagreement among judges is enough; no key has to be compromised. The owner's only rescue is setJudges with fresh addresses so new votes can reach 17 in the same round, if it acts in time and has not renounced. Fix sketch: once approveVotes + denyVotes reaches JUDGE_COUNT, decide from the median as the brief says.","line":317,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PLEAToken} from \"src/PLEAToken.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract MockIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Anyone may route a swap or a liquidity change through this. Liquidity is keyed to the caller,\n/// as a public position manager does; to the pool manager the `sender` is always this contract.\ncontract Router is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager manager_) {\n        manager = manager_;\n    }\n\n    function deployToken() external returns (PLEAToken) {\n        return new PLEAToken();\n    }\n\n    function deploy(bytes memory code, bytes32 salt) external returns (address deployed) {\n        assembly (\"memory-safe\") {\n            deployed := create2(0, add(code, 32), mload(code), salt)\n        }\n        require(deployed != address(0), \"deploy failed\");\n    }\n\n    function initialize(PoolKey memory key, uint160 price) external {\n        manager.initialize(key, price);\n    }\n\n    function send(IERC20 token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n\n    function swap(PoolKey memory key, bool zeroForOne, uint256 amountIn) external {\n        manager.unlock(abi.encode(true, msg.sender, key, zeroForOne, amountIn, int24(0), int24(0), int256(0)));\n    }\n\n    function modify(PoolKey memory key, int24 lower, int24 upper, int256 liquidity) external {\n        manager.unlock(abi.encode(false, msg.sender, key, false, uint256(0), lower, upper, liquidity));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (bool isSwap, address who, PoolKey memory key, bool z, uint256 amt, int24 lo, int24 hi, int256 liq) =\n            abi.decode(data, (bool, address, PoolKey, bool, uint256, int24, int24, int256));\n        BalanceDelta delta;\n        if (isSwap) {\n            delta = manager.swap(\n                key,\n                SwapParams({\n                    zeroForOne: z,\n                    amountSpecified: -int256(amt),\n                    sqrtPriceLimitX96: z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n                }),\n                \"\"\n            );\n        } else {\n            (delta,) = manager.modifyLiquidity(\n                key,\n                ModifyLiquidityParams({\n                    tickLower: lo,\n                    tickUpper: hi,\n                    liquidityDelta: liq,\n                    salt: bytes32(uint256(uint160(who)))\n                }),\n                \"\"\n            );\n        }\n        _settle(key.currency0, delta.amount0(), who);\n        _settle(key.currency1, delta.amount1(), who);\n        return \"\";\n    }\n\n    function _settle(Currency currency, int128 amount, address who) private {\n        if (amount < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transferFrom(who, address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(currency, who, uint256(uint128(amount)));\n        }\n    }\n}\n\nabstract contract Fixture is Test {\n    address internal owner = makeAddr(\"owner\");\n    address internal seller = makeAddr(\"seller\");\n    address internal stranger = makeAddr(\"stranger\");\n    address internal buyer = makeAddr(\"buyer\");\n\n    PoolManager internal manager;\n    MockIMD internal imd;\n    Router internal factory;\n    PLEAToken internal token;\n    PleaHook internal hook;\n    PleaCourt internal court;\n    PoolKey internal key;\n    address[] internal judges;\n\n    function setUp() public virtual {\n        manager = new PoolManager(address(this));\n        imd = new MockIMD();\n        factory = new Router(manager);\n        token = factory.deployToken();\n        bytes memory code =\n            abi.encodePacked(type(PleaHook).creationCode, abi.encode(manager, address(token), owner, address(factory)));\n        uint160 flags = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_ADD_LIQUIDITY | HookFlags.BEFORE_SWAP;\n        bytes32 hash = keccak256(code);\n        bytes32 salt;\n        for (uint256 i = 0; i < 500_000; i++) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(factory), bytes32(i), hash)))));\n            if (HookFlags.matches(p, flags)) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        hook = PleaHook(factory.deploy(code, salt));\n        court = hook.court();\n        (address c0, address c1) =\n            address(token) < address(imd) ? (address(token), address(imd)) : (address(imd), address(token));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12_500, 60, IHooks(address(hook)));\n        factory.initialize(key, 79228162514264337593543950336);\n\n        imd.transfer(address(factory), 1e24);\n        factory.send(IERC20(address(token)), address(this), 1e26);\n        token.approve(address(factory), type(uint256).max);\n        imd.approve(address(factory), type(uint256).max);\n        factory.modify(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1e21);\n\n        factory.send(IERC20(address(token)), seller, 1e24);\n        factory.send(IERC20(address(token)), stranger, 1e24);\n        imd.transfer(buyer, 1e24);\n        imd.transfer(stranger, 1e24);\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n    }\n\n    function installCabal() internal {\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function isSellZeroForOne() internal view returns (bool) {\n        return Currency.unwrap(key.currency0) == address(token);\n    }\n\n    function vote(uint256 id, uint256 from, uint256 to, uint8 score, uint8 facts) internal {\n        for (uint256 i = from; i < to; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, score, facts);\n        }\n    }\n\n    function plead(address who, uint256 amount) internal returns (uint256 id) {\n        vm.prank(who);\n        id = court.submitPlea(\"the facts: the sky is blue\", amount);\n    }\n}\n\n/// @dev All 30 judges vote, 15 on each side. Neither side reaches 17, nobody can vote again, and only the\n/// seller can clear the plea. It stays Pending, the dead-man clock keeps running and the gate falls for good.\ncontract SplitPanelTest is Fixture {\n    function test_fullPanelSplitMustNotFreeTrading() public {\n        installCabal();\n        uint256 id = plead(seller, 1e18);\n        vote(id, 0, 15, 45, 26); // 15 approvals\n        vote(id, 15, 30, 10, 0); // 15 denials: every judge has now voted\n\n        vm.warp(block.timestamp + 33 hours);\n        assertFalse(court.tradesFreely(), \"a full panel voted, yet its plea hung and freed trading forever\");\n    }\n}","reproduction":"Call setJudges(30 addresses). seller calls submitPlea('x', 1e18). judges[0..14] call judge(id, 45, 26) (approve), judges[15..29] call judge(id, 10, 0) (deny). State: status Pending, approveVotes 15, denyVotes 15; every further judge() reverts AlreadyVoted. Warp 33 hours. Expected: a verdict from the median and tradesFreely() false. Actual: no Verdict event, tradesFreely() true, every router sell passes beforeSwap forever.","severity":"high","snippet":"        if (p.approveVotes >= QUORUM) _decide(pleaId, p, true);\n        else if (p.denyVotes >= QUORUM) _decide(pleaId, p, false);","title":"A full panel can split with no verdict: the plea hangs Pending, only its seller can clear it, and the dead-man switch fires"},{"citation":"resolved","description":"Guard gap on withdrawPlea compared with judge(). The switch keys on verdicts (lastVerdictAt), but a seller may withdraw a Pending plea at any vote count, discarding every vote cast, and no verdict is recorded. _leavePending only clears pendingSince when the count reaches 0, so if another plea is pending the original pendingSince keeps ageing. A few wallets (no PLEA balance needed, since _submit never checks one) keep overlapping pleas and each withdraws before its 17th vote. The Cabal does its work and the 33-hour silence condition is still met, so the permanent escape hatch can be forced by anyone during a stretch with no honest verdict. SECURITY.md section 4 claims 'A Cabal that judges at all is never tripped by spam'; that holds only if a verdict can land. Same call also lets any seller dodge a denial. The 17 votes are separate transactions, so a seller who cannot race the 17th can withdraw at a lower count. An honest plea reaching a verdict in those 33 hours resets the clock, so the attack needs a quiet period such as the early days of the launch. Fix sketch: refuse withdrawal once the current round has votes, or let withdrawal only happen after a timeout.","line":363,"path":"src/PleaCourt.sol","reproduction":"setJudges(30). t0: wallet0 submitPlea. Every 6 hours for i in 1..6: wallet_i submitPlea (so pendingCount never reaches 0); judges[0..15] call judge(previousId, 45, 26); wallet_{i-1} calls withdrawPlea(previousId); judges[16] calling judge(previousId, 45, 26) reverts WrongStatus(Withdrawn). At t0+36h: 96 votes cast, lastVerdictAt 0, pendingSince t0. Expected: the Cabal is demonstrably alive and tradesFreely() false. Actual: tradesFreely() true, permanently.","severity":"high","snippet":"        if (p.status == Status.Pending) _leavePending();\n        else if (p.status != Status.Denied) revert WrongStatus(p.status);","title":"A seller can withdraw a plea mid-vote, so sellers can stop any verdict landing and time out a live Cabal, freeing PLEA forever at gas cost"},{"citation":"resolved","description":"Guard gap in _submit: it never checks cabalConfigured(), and error CabalNotConfigured (line 67) is declared but never used, so this guard exists in name only (the aderyn unused-error lead). The factory's launch transaction cannot call setJudges because that is the owner's, so every launch opens with zero judges. Any wallet can plead in the first block, which sets pendingSince. judge() needs a panel, so the first verdict needs setJudges and then 17 votes, all inside 33 hours. README line 156 tells the owner 'set the judges before the first plea or within 33 hours of it', which is wrong: installing the panel leaves the deadline unchanged, only a verdict moves it. Severity is medium rather than high because a prepared owner has 33 hours to get a verdict through, but the failure is permanent and the plea costs a stranger only gas. Fix sketch: revert CabalNotConfigured in _submit until the panel is set, or start the clock from setJudges.","line":273,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.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, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PLEAToken} from \"src/PLEAToken.sol\";\nimport {PleaHook} from \"src/PleaHook.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\ncontract MockIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @dev Anyone may route a swap or a liquidity change through this. Liquidity is keyed to the caller,\n/// as a public position manager does; to the pool manager the `sender` is always this contract.\ncontract Router is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager manager_) {\n        manager = manager_;\n    }\n\n    function deployToken() external returns (PLEAToken) {\n        return new PLEAToken();\n    }\n\n    function deploy(bytes memory code, bytes32 salt) external returns (address deployed) {\n        assembly (\"memory-safe\") {\n            deployed := create2(0, add(code, 32), mload(code), salt)\n        }\n        require(deployed != address(0), \"deploy failed\");\n    }\n\n    function initialize(PoolKey memory key, uint160 price) external {\n        manager.initialize(key, price);\n    }\n\n    function send(IERC20 token, address to, uint256 amount) external {\n        token.transfer(to, amount);\n    }\n\n    function swap(PoolKey memory key, bool zeroForOne, uint256 amountIn) external {\n        manager.unlock(abi.encode(true, msg.sender, key, zeroForOne, amountIn, int24(0), int24(0), int256(0)));\n    }\n\n    function modify(PoolKey memory key, int24 lower, int24 upper, int256 liquidity) external {\n        manager.unlock(abi.encode(false, msg.sender, key, false, uint256(0), lower, upper, liquidity));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (bool isSwap, address who, PoolKey memory key, bool z, uint256 amt, int24 lo, int24 hi, int256 liq) =\n            abi.decode(data, (bool, address, PoolKey, bool, uint256, int24, int24, int256));\n        BalanceDelta delta;\n        if (isSwap) {\n            delta = manager.swap(\n                key,\n                SwapParams({\n                    zeroForOne: z,\n                    amountSpecified: -int256(amt),\n                    sqrtPriceLimitX96: z ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n                }),\n                \"\"\n            );\n        } else {\n            (delta,) = manager.modifyLiquidity(\n                key,\n                ModifyLiquidityParams({\n                    tickLower: lo,\n                    tickUpper: hi,\n                    liquidityDelta: liq,\n                    salt: bytes32(uint256(uint160(who)))\n                }),\n                \"\"\n            );\n        }\n        _settle(key.currency0, delta.amount0(), who);\n        _settle(key.currency1, delta.amount1(), who);\n        return \"\";\n    }\n\n    function _settle(Currency currency, int128 amount, address who) private {\n        if (amount < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transferFrom(who, address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(currency, who, uint256(uint128(amount)));\n        }\n    }\n}\n\nabstract contract Fixture is Test {\n    address internal owner = makeAddr(\"owner\");\n    address internal seller = makeAddr(\"seller\");\n    address internal stranger = makeAddr(\"stranger\");\n    address internal buyer = makeAddr(\"buyer\");\n\n    PoolManager internal manager;\n    MockIMD internal imd;\n    Router internal factory;\n    PLEAToken internal token;\n    PleaHook internal hook;\n    PleaCourt internal court;\n    PoolKey internal key;\n    address[] internal judges;\n\n    function setUp() public virtual {\n        manager = new PoolManager(address(this));\n        imd = new MockIMD();\n        factory = new Router(manager);\n        token = factory.deployToken();\n        bytes memory code =\n            abi.encodePacked(type(PleaHook).creationCode, abi.encode(manager, address(token), owner, address(factory)));\n        uint160 flags = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_ADD_LIQUIDITY | HookFlags.BEFORE_SWAP;\n        bytes32 hash = keccak256(code);\n        bytes32 salt;\n        for (uint256 i = 0; i < 500_000; i++) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(factory), bytes32(i), hash)))));\n            if (HookFlags.matches(p, flags)) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        hook = PleaHook(factory.deploy(code, salt));\n        court = hook.court();\n        (address c0, address c1) =\n            address(token) < address(imd) ? (address(token), address(imd)) : (address(imd), address(token));\n        key = PoolKey(Currency.wrap(c0), Currency.wrap(c1), 12_500, 60, IHooks(address(hook)));\n        factory.initialize(key, 79228162514264337593543950336);\n\n        imd.transfer(address(factory), 1e24);\n        factory.send(IERC20(address(token)), address(this), 1e26);\n        token.approve(address(factory), type(uint256).max);\n        imd.approve(address(factory), type(uint256).max);\n        factory.modify(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1e21);\n\n        factory.send(IERC20(address(token)), seller, 1e24);\n        factory.send(IERC20(address(token)), stranger, 1e24);\n        imd.transfer(buyer, 1e24);\n        imd.transfer(stranger, 1e24);\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n    }\n\n    function installCabal() internal {\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function isSellZeroForOne() internal view returns (bool) {\n        return Currency.unwrap(key.currency0) == address(token);\n    }\n\n    function vote(uint256 id, uint256 from, uint256 to, uint8 score, uint8 facts) internal {\n        for (uint256 i = from; i < to; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, score, facts);\n        }\n    }\n\n    function plead(address who, uint256 amount) internal returns (uint256 id) {\n        vm.prank(who);\n        id = court.submitPlea(\"the facts: the sky is blue\", amount);\n    }\n}\n\n/// @dev Nothing stops a plea before the Cabal exists, and the launch cannot install it: `setJudges` is the\n/// owner's, after the factory's transaction. Any wallet starts the clock in the first block, and installing\n/// the panel does not stop it.\ncontract PleaBeforeCabalTest is Fixture {\n    function test_pleaBeforeCabalMustNotStartTheClock() public {\n        vm.prank(stranger);\n        try court.submitPlea(\"first block\", 1) {}\n        catch {\n            return; // refused until the panel exists: the gate holds\n        }\n        // The README's instruction: \"set the judges ... within 33 hours of it\".\n        vm.warp(block.timestamp + 32 hours);\n        installCabal();\n        vm.warp(block.timestamp + 1 hours);\n        assertFalse(court.tradesFreely(), \"one stranger's plea before the Cabal existed freed trading forever\");\n    }\n}","reproduction":"Fresh launch with no setJudges. stranger calls court.submitPlea('first block', 1). Warp 32 hours; owner calls court.setJudges(30 addresses), following the README. Warp 1 hour. Expected: trading still gated, because a Cabal that never existed cannot have gone silent and the panel is now live. Actual: tradesFreely() true at T+33h; judge() now reverts FreeTrading, so the Cabal cannot recover it.","severity":"medium","snippet":"        _latchDeadMan();\n        if (deadManTriggered) revert FreeTrading();\n        if (amount == 0 || amount > type(uint128).max) revert ZeroAmount();","title":"Anyone can start the irreversible dead-man clock before the Cabal exists, and installing the panel does not stop it"},{"citation":"resolved","description":"Confused deputy. In beforeAddLiquidity, sender is whichever contract called modifyLiquidity. setLiquidityProvider's comment ('a position manager, say') and README step 4 both recommend allowlisting a position manager, but that is a shared contract: once listed, every user passes the check. Any holder can then place single-sided PLEA liquidity on the buy side of the price, let buyers fill it, and remove it for IMD: exactly the sell without a plea that this callback exists to stop, and removal has no callback. It needs the owner to take the documented step, hence medium; while listed, the gate is open to everyone. The current test only covers a single-purpose LP contract (test/PleaHook.t.sol:299). Fix sketch: check the real LP (the v4-periphery msgSender() convention on a trusted router, then an allowlist of end users), or drop the allowlist and correct README step 4.","line":136,"path":"src/PleaHook.sol","reproduction":"Owner calls hook.setLiquidityProvider(positionManager, true). stranger (holding PLEA, no plea, activePlea 0) approves the manager and adds 1e20 liquidity at ticks 60..6000 (PLEA is currency0) or -6000..-60 (PLEA is currency1), a range holding only PLEA. buyer swaps 1e21 IMD into PLEA through any router, crossing the range. stranger removes -1e20. Expected: the add refuses, or no IMD can reach a wallet with no plea. Actual: stranger's IMD rises from 1000000e18 to about 1000035.12e18 and PLEA falls, with no plea ever submitted.","severity":"medium","snippet":"        if (sender != factory && !liquidityProviders[sender] && !court.tradesFreely()) revert LiquidityRestricted();","title":"The liquidity allowlist checks the router and not the LP, so allowlisting a position manager as README step 4 advises opens a plea-free sell route to everyone"},{"citation":"resolved","description":"Pair: appeal() against withdrawPlea()+submitPlea(). The brief says DENIED allows one appeal after 4 hours. appeal() enforces both (lines 343, 346-347). withdrawPlea accepts status Denied, then clears activePlea, and _submit keeps no memory of denials, so the same seller and same text starts a new round 1 in the same block. README line 79-80 documents the withdrawal route but not that it voids the appeal rule: AppealTooEarly and AlreadyAppealed only bind sellers who choose to be bound. Each re-hearing also costs the Cabal 17 more transactions. Fix sketch: record a per-seller earliest next plea time on denial and keep it through withdrawal.","line":364,"path":"src/PleaCourt.sol","reproduction":"setJudges(30). seller submitPlea('x', 1e18) = id1; judges[0..16] judge(id1, 10, 0) gives Denied. seller appeal(id1) reverts AppealTooEarly. Same block: seller withdrawPlea(id1), submitPlea('x', 1e18) = id2, judged 10/0 and denied again; withdrawPlea(id2), submitPlea = id3; judges[0..16] judge(id3, 45, 26) gives Approved and hook.sell(1e18, 0) succeeds. Expected: at most two hearings, the second no sooner than 4 hours later. Actual: three hearings at the same timestamp, no limit.","severity":"medium","snippet":"        else if (p.status != Status.Denied) revert WrongStatus(p.status);","title":"Denied, then withdraw and resubmit: the one-appeal limit and the 4-hour delay are skipped, with unlimited immediate re-hearings"},{"citation":"resolved","description":"Inconsistent guard across functions. bannedUntil is read only in _submit (the writer is ruleObjection, line 427). authorizeSale, appeal and judge/_decide never read it, and ruleObjection does not close the seller's active plea. So an upheld objection does not stop the seller: the rest of the open window still sells, a pending plea can be approved and sold in full, a denied plea can be appealed, and _award re-credits points right after _strike voided them, putting the seller back on the board. README line 82 says the seller is 'banned from pleading for 7 days'; a seller who keeps a plea in flight is not. Fix sketch: check the ban in authorizeSale, appeal and _decide, or close the active plea when an objection is upheld.","line":278,"path":"src/PleaCourt.sol","reproduction":"Three reproduced paths, each after 17 ruleObjection(o, true) with bannedUntil > now. (a) Plea for 2e18 approved, sell(1e18,0) = saleId, stranger object(saleId), upheld inside the 7 minutes: sell(1e18,0) still succeeds. (b) Plea for 1e18 approved and fully sold; new plea id2 for 5e18 submitted; first sale's objection upheld, seasonPoints(1, seller) 0: judges[0..16] judge(id2, 45, 26) gives Approved, seasonPoints back to 45, sell(5e18,0) succeeds. (c) As (b) but id2 denied by 17 judge(id2,10,0): after 4 hours, still banned, appeal(id2) succeeds and status is Pending. Expected: all refused during the ban. Actual: all succeed.","severity":"medium","snippet":"        if (bannedUntil[seller] > block.timestamp) revert Banned(bannedUntil[seller]);","title":"A ban is only checked in _submit: a banned seller keeps their open window, their in-flight plea, their appeal, and earns new points"},{"citation":"resolved","description":"Trust gap (access x economics x timing). _strike zeroes seasonPoints[season], the season current when the ruling lands, not the season of the sale, while endSeason is permissionless and ruleObjection has up to 48 hours or more. Sale records hold no season. Anyone, including the seller, can call endSeason between the objection and the ruling, which credits the prize; claimable is never reduced by a strike. So a sale near season end escapes the penalty and the penalty lands on whatever the seller earned in the next season, an unrelated period. Fix sketch: store the season on the Sale, strike that season, and hold or claw back the seller's unclaimed prize while one of their objections is open.","line":525,"path":"src/PleaCourt.sol","reproduction":"Owner setPotFeeBps(1000). At seasonStart+30d-10min: seller's plea approved at score 45/facts 26, sell(1e18,0)=saleId; stranger object(saleId)=o. At +30d: anyone calls endSeason(), crediting seller the whole pot. Seller's plea in season 2 approved, seasonPoints(2, seller)=45. Then 17 judges ruleObjection(o, true). Expected: season 1's 45 points voided and the prize withheld; season 2 untouched. Actual: seasonPoints(1, seller) stays 45, claimable is unchanged and claim() pays out, seasonPoints(2, seller) becomes 0.","severity":"medium","snippet":"        seasonPoints[season][account] = 0;","title":"An upheld objection strikes the current season, not the sale's: a seller can end the season first and keep the prize, and honest new-season points are wiped"},{"citation":"resolved","description":"Branch asymmetry in sell: the free branch skips authorizeSale but unlockCallback still charges potFeeBps, although line 69 and README line 118 describe a fee on gated sales only, and router sells at that point pay nothing. After the switch _submit and judge revert FreeTrading, so _award can never run. The first endSeason empties the board; every later endSeason sees totalPoints 0 and rolls the pot on. The court has no sweep, so the fee (up to 10% of proceeds) and any later forfeited bond are stuck for good. If the board is empty when the switch fires, the whole existing pot is stuck too. Fix sketch: charge no fee when free is true, and give the post-switch pot a final destination.","line":188,"path":"src/PleaHook.sol","reproduction":"Owner setPotFeeBps(1000). stranger submitPlea('x', 1e18); warp 33 hours; triggerDeadManSwitch(). seller approves hook and calls hook.sell(1e18, 0). prizePot() is now 10% of the proceeds. submitPlea reverts FreeTrading. Warp 30 days, endSeason(); warp 30 days, endSeason(). Expected: no fee on a free sale, or a way to distribute it. Actual: prizePot() unchanged after both season ends, with no function able to move it.","severity":"medium","snippet":"        uint256 fee = out * potFeeBps / BPS;","title":"After the dead-man switch, hook.sell still takes the pot fee, and that IMD is locked forever because nobody can ever earn points again"},{"citation":"resolved","description":"The brief says anyone can object. object() lets the first caller take the only slot for 1 IMD, and s.objected is never cleared when an objection resolves, so a dismissed or reclaimed objection bars every honest objector even inside the 20 minutes. Paired with this, reclaimObjectionBond (the objector's) and ruleObjection (the judges') are both live after 48 hours with no precedence, so a seller-controlled objector can end a nearly upheld ruling for free. Low because 17 judges can still uphold within 48 hours, and an objection carries no reason text for an honest objector to add.","line":390,"path":"src/PleaCourt.sol","reproduction":"Plea approved, seller sell(1e18,0)=saleId. Seller calls object(saleId)=o themselves. stranger object(saleId) reverts AlreadyObjected. judges[0..15] ruleObjection(o,true) (16 upholds). Warp 48 hours. Seller reclaimObjectionBond(o). judges[16] ruleObjection(o,true) reverts ObjectionResolvedAlready. Expected: the honest objector can post and the 17th vote counts. Actual: bannedUntil(seller) 0, seasonPoints(1, seller) still 45, bond credited back.","severity":"low","snippet":"        if (s.objected) revert AlreadyObjected();\n        if (block.timestamp > s.executedAt + OBJECTION_WINDOW) revert ObjectionWindowClosed();\n        if (address(imd) == address(0)) revert NotBound();\n        s.objected = true;","title":"One objection slot per sale, held by whoever gets there first: a seller's own wallet can take it and later abort the ruling"},{"citation":"resolved","description":"Trust gap (access x economics). _award runs at verdict, and the only way to void points is an objection to a Sale. A plea for 1 wei from a wallet with no PLEA is accepted (_submit checks no balance), and if approved it creates points without a sale, so there is nothing to object to. The objection check the brief relies on therefore never applies to those points. Low because 17 judges must approve it. Fix sketch: award points on the sale (authorizeSale), scaled or thresholded by the amount sold.","line":331,"path":"src/PleaCourt.sol","reproduction":"makeAddr('nobody') has token balance 0. nobody submitPlea('x', 1); judges[0..16] judge(id, 45, 26): approved, seasonPoints(1, nobody)=45, saleCount 0. Warp past 7 minutes, closeWindow(id). 100e18 IMD reaches the court; warp 30 days; endSeason(). Expected: leaderboard prizes for sellers whose sales stood the objection window. Actual: claimable(nobody) = 100e18, the whole pot.","severity":"low","snippet":"            _award(p.seller, median);","title":"Prize points come from approval alone: a wallet holding no PLEA, which never sells, earns them and no objection can ever reach it"},{"citation":"resolved","description":"Pair: post() against postSigned(). Only postSigned consumes nonces, and no function invalidates one. A seller who signs for a relayer and then pleads directly leaves the signature live; anyone who saw it can post the old text and amount later, taking the seller's active slot (they must withdraw to plead again) and starting a dead-man clock in their name. Bounded by the deadline the seller signed, and no funds move. Fix sketch: add cancelNonce, callable by the seller.","line":45,"path":"src/Letterbox.sol","reproduction":"signer signs Plea(text='old case', amount=1e18, nonce=0, deadline=now+30d) for a relayer. signer instead calls box.post('new case', 5e18), which is approved and closed; nonces(signer) stays 0. At +10 days stranger calls box.postSigned(signer, 'old case', 1e18, deadline, sig). Expected: refused because superseded, or cancellable. Actual: activePlea(signer) is the old text's plea and signer's box.post('today', 1e18) reverts ActivePleaExists.","severity":"low","snippet":"        uint256 nonce = nonces[seller]++;","title":"A signed plea cannot be cancelled: post() does not touch the nonce, so an abandoned signature can be posted at any time until its deadline"},{"citation":"resolved","description":"Access x economics. judge() checks isJudge and hasScored but never msg.sender != p.seller; ruleObjection likewise does not exclude the objector or the seller of the sale. A judge can credit themselves the maximum on their own plea, which also feeds their own points. One vote of the 17 needed, so low; the fix is one comparison in each function.","line":299,"path":"src/PleaCourt.sol","reproduction":"setJudges with judges[0] on the panel. judges[0] calls submitPlea('x', 1e18)=id, then judge(id, 45, 71). Expected: refused as self-judging. Actual: hasScored(id,1,judges[0]) true and plea(id).approveVotes 1.","severity":"low","snippet":"    function judge(uint256 pleaId, uint8 score, uint8 factPoints) external onlyJudge {\n        _latchDeadMan();\n        if (deadManTriggered) revert FreeTrading();\n        Plea storage p = _pleas[pleaId];\n        if (p.status != Status.Pending) revert WrongStatus(p.status);\n        if (score > MAX_SCORE) revert ScoreTooHigh();\n        if (factPoints > MAX_FACT_POINTS) revert FactPointsTooHigh();\n        uint8 round = p.round;\n        if (hasScored[pleaId][round][msg.sender]) revert AlreadyVoted();","title":"No recusal: a judge may score their own plea and rule on an objection they raised"},{"citation":"resolved","description":"Documented as a trust assumption, not a bypass. A one-transaction, no-delay setJudges replaces the whole Cabal, and votes already cast stand, so a round can exceed 30 votes and the panel the brief calls 'IMD oracle judges' is whatever the owner names. SECURITY.md line 36 says the owner cannot 'end the gate early', which is not so: the owner can install keys it holds and approve its own unlimited plea, or install keys that never vote so the switch fires. It also can defer the switch indefinitely by feeding its own panel verdicts. Other powers: setSeat is read live at each vote, so toggling it mid-round mixes scaled and unscaled scores for one plea. renounceOwnership before setJudges means no panel can ever exist; after it, no judge key can be replaced. transferOwnership is one-step, and also hands over the panel, since the court reads the owner live. The owner-side recommendation in SECURITY.md open item 2 (multisig or timelock) still stands; correct line 36.","line":234,"path":"src/PleaCourt.sol","reproduction":"plea from owner; judges[0..15] judge(id, 10, 0) (16 denials). owner setJudges(30 fresh addresses). 17 of them judge(id, 45, 71). Status Approved, scores(id,1).length 33. Separately: owner renounceOwnership(), then setJudges reverts NotOwner for everyone.","severity":"info","snippet":"    function setJudges(address[] calldata panel) external onlyOwner {","title":"Owner trust assumptions, one of which SECURITY.md misstates: the owner can decide any plea and can end the gate"}],"hash":"b47c1bd9357989ba54c486539fdadcbd202d17f6c63371a56eeed24474a4db16","nodeId":"4b3db674-f29b-41c0-b806-a78ed02f7c31","outcome":"completed","summary":"I finished the permissions audit and wrote 13 findings to `.imd-findings.json`: 2 high, 6 medium, 4 low and 1 info. No source files changed, and `test/scratch/` is removed.\n\n## Findings\n\n| # | Severity | Defect | Trigger |\n|---|---|---|---|\n| 1 | High | A full panel can split with no verdict, so the plea hangs and the dead-man switch fires | 15 judges approve and 15 deny, then 33 hours pass |\n| 2 | High | Withdrawing mid-vote stops any verdict landing, so sellers can time out a working Cabal | Overlapping pleas, each withdrawn at 16 votes, with no honest verdict for 33 hours |\n| 3 | Medium | A plea before the Cabal exists starts the dead-man clock, and `setJudges` doesn't stop it | A stranger pleads in block 1; the owner installs judges at 32 hours as the README says |\n| 4 | Medium | The liquidity allowlist checks the router, not the LP, so it opens a sell route with no plea | Owner allowlists a position manager as README step 4 advises; anyone adds PLEA-only liquidity, buyers fill it, they remove it for IMD |\n| 5 | Medium | Withdrawing a denied plea and resubmitting skips the one-appeal limit and 4-hour delay | Three hearings at the same timestamp |\n| 6 | Medium | A ban is only checked when submitting a plea | A banned seller can still sell, get a pending plea approved, appeal and earn points |\n| 7 | Medium | An upheld objection voids points in the current season, not the sale's | `endSeason` lands before the ruling: the seller keeps the prize and loses new-season points instead |\n| 8 | Medium | After the dead-man switch, `hook.sell` still takes the pot fee, and that IMD is locked forever | A sale after the switch, then repeated `endSeason` calls |\n| 9 | Low | The first objector holds the only slot, and can abort a ruling after 48 hours | The seller objects to their own sale |\n| 10 | Low | Points come from approval alone, with no sale for anyone to object to | A wallet with no PLEA pleads for 1 wei and takes the whole pot |\n| 11 | Low | A signed plea can't be cancelled | An old signature is posted 10 days later and blocks the seller's next plea |\n| 12 | Low | Judges can vote on their own plea or objection | A judge scores their own plea |\n| 13 | Info | Owner trust assumptions; `SECURITY.md:36` wrongly says the owner can't end the gate | The owner swaps in their own panel mid-round |\n\n## How far each result was checked\n- **Reproduced:** I reproduced findings 1–12 and the `setJudges` and `renounceOwnership` examples in finding 13 with Foundry tests. The other owner powers in finding 13 come from reading the code.\n- **Proofs:** findings 1–4 carry proof tests. Each fails on the current code for the stated reason and passes with a minimal fix applied in a throwaway copy outside the repo.\n- **Fix side effect:** with those fixes, one existing test failed (`test_strangerCannotAddLiquidity`). My crude fix for finding 4 removed the allowlist check, and that test appears to rely on it.\n\n## Coverage\n- **Entry points:** all 28 have a row: 16 `finding`, 12 `holds`, none `unreached`. Another 4 rows record invariants that held.\n- **Passes:** I made the Access Control, Trust Gap and Asymmetry passes myself, with no helpers.\n- **What held:**\n    - Callback caller checks and `beforeInitialize`'s factory and single-pool binding.\n    - `unlockCallback` reachability.\n    - Buys are never blocked.\n    - The router sell gate, including exact-output swaps and `hookData`.\n    - The dead-man switch stays on once due.\n    - Letterbox replay and forgery protection.\n- **Static analysis:**\n    - The strict-equality, uninitialized-local and `settle()` lines are benign.\n    - The unused `CabalNotConfigured` error is real and supports finding 3.\n    - The timestamp lines only matter through the timing findings above.\n\n## Not covered\n- **Other specialists' areas:** I didn't make the math or economics passes. How the median relates to the bar, and how the first 17 votes affect the median, are left to those reviewers.\n- **Launch factory:** it isn't in this tree,","treeHash":null,"usage":{"cachedInputTokens":1003957,"inputTokens":19,"model":"claude-opus-5-5","outputTokens":61630,"runtime":"claude","turns":25,"wallClockMs":734700}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"20e8e883cb88740a","findings":[],"hash":"f8b8c15cbe3423893861bed051a2d04901827d7dd57c80da062c2fbe0b95fbb8","nodeId":"b441330e-f6c2-4b2f-a999-e35755e414a9","outcome":"failed","summary":"API Error: Your account can't receive replies from Claude right now. You can find the next step in the Anthropic Verification Portal: https://portal.anthropic.com/…","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":1,"wallClockMs":2151}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f5666f1d1aa75678","findings":[{"citation":"resolved","description":"Regression introduced by this round's fixes. judge() now reverts Recused when msg.sender == p.seller (line 356), so a plea submitted by one of the 30 judges can collect at most 29 votes. The new hung-Cabal rule at line 372 settles a plea only when approveVotes + denyVotes >= JUDGE_COUNT (30). A 29-vote split with neither side at 17 (15/14, 14/15, 13/16, 16/13) therefore has no settlement path: every further judge() reverts AlreadyVoted, appeal() reverts WrongStatus(Pending), and the seller cannot withdraw it either because withdrawPlea now reverts VotesCast once any score is recorded (line 432). The plea stays Pending, pendingCount stays 1, lastVerdictAt is not updated, and deadManDeadline() runs out 33 hours later unless another plea gets a verdict; tradesFreely() then becomes true and PleaHook.beforeSwap (src/PleaHook.sol line 148) lets every sell through permanently. This is the same failure the earlier high finding dd7e9df2 described, re-opened for the narrower case of a judge who pleads; an honest split on a borderline plea is enough and the seller-judge cannot clear it. The owner's only rescue is setJudges inside 33 hours with a panel that replaces the seller so a 30th address can vote. Medium rather than high because the precondition is specific (the seller is a judge) and the owner can still rescue it in time. Fix: settle when every judge who may vote has voted, e.g. compare against JUDGE_COUNT minus one when isJudge[p.seller], or track the number of eligible judges per round; alternatively refuse pleas from judges in _submit.","line":372,"path":"src/PleaCourt.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PleaCourt} from \"src/PleaCourt.sol\";\nimport {IPleaCourt} from \"src/interfaces/IPleaCourt.sol\";\n\n/// @dev Stands in for PleaHook: the court only needs `owner()` from it for administration.\ncontract HookStub {\n    address public owner;\n\n    constructor(address owner_) {\n        owner = owner_;\n    }\n}\n\n/// @notice A judge who pleads is recused from their own plea, so at most 29 votes can ever land on it.\n/// The hung-Cabal rule settles a plea only at 30 votes, so a 29-vote split (15 approve / 14 deny) leaves\n/// the plea Pending forever: no judge can vote again, the seller cannot withdraw it (VotesCast), and\n/// the dead-man clock runs out 33 hours later although every eligible judge voted.\n/// Fails on the current code; passes once the hung-Cabal rule counts the judges who may vote.\ncontract JudgeSellerHangTest is Test {\n    PleaCourt court;\n    address owner = makeAddr(\"owner\");\n    address[] judges;\n\n    function setUp() public {\n        HookStub hook = new HookStub(owner);\n        court = new PleaCourt(address(hook));\n        for (uint256 i = 0; i < 30; i++) {\n            judges.push(makeAddr(string.concat(\"judge\", vm.toString(i))));\n        }\n        vm.prank(owner);\n        court.setJudges(judges);\n    }\n\n    function test_judgeSellerPleaCannotBeSettledByTheRemaining29() public {\n        address seller = judges[0];\n        vm.prank(seller);\n        uint256 id = court.submitPlea(\"a judge's own plea\", 1e18);\n\n        vm.prank(seller);\n        vm.expectRevert(PleaCourt.Recused.selector);\n        court.judge(id, 45, 26);\n\n        // 15 approve (45 + 26 = 71), 14 deny (44 + 26 = 70): all 29 eligible judges voted.\n        for (uint256 i = 1; i < 16; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 45, 26);\n        }\n        for (uint256 i = 16; i < 30; i++) {\n            vm.prank(judges[i]);\n            court.judge(id, 44, 26);\n        }\n\n        // Nobody can add a 30th vote and the seller cannot withdraw a scored plea.\n        vm.prank(judges[1]);\n        vm.expectRevert(PleaCourt.AlreadyVoted.selector);\n        court.judge(id, 45, 26);\n        vm.prank(seller);\n        vm.expectRevert(PleaCourt.VotesCast.selector);\n        court.withdrawPlea(id);\n\n        assertTrue(\n            court.pleaStatus(id) != IPleaCourt.Status.Pending,\n            \"29 votes cast, 15 approve / 14 deny, no 30th vote possible: the plea has no verdict\"\n        );\n        assertEq(court.pendingCount(), 0, \"an undecidable plea keeps the dead-man clock running\");\n\n        vm.warp(block.timestamp + 33 hours);\n        assertFalse(court.tradesFreely(), \"the sell gate was lost although every eligible judge voted\");\n    }\n}","reproduction":"test/scratch/JudgeSellerHang.t.sol (attached): HookStub owner; court; setJudges(30). judges[0] submitPlea('a judge's own plea', 1e18) = id. judges[0] judge(id,45,26) reverts Recused. judges[1..15] judge(id,45,26) (15 approve, 45+26=71); judges[16..29] judge(id,44,26) (14 deny). Then judges[1] judge(id,...) reverts AlreadyVoted and judges[0] withdrawPlea(id) reverts VotesCast. Expected: a verdict after all 29 eligible judges voted, pendingCount 0, tradesFreely() false 33h later. Actual: pleaStatus(id) == Pending, pendingCount 1, and after vm.warp(+33h) tradesFreely() == true. The test fails with '29 votes cast, 15 approve / 14 deny, no 30th vote possible: the plea has no verdict'.","severity":"medium","snippet":"        else if (uint256(p.approveVotes) + p.denyVotes >= JUDGE_COUNT) _decide(pleaId, p, false); // hung Cabal","title":"A judge's own plea can never be settled: recusal caps it at 29 votes while the hung-Cabal rule needs 30, so it hangs Pending, cannot be withdrawn, and trips the permanent dead-man switch"},{"citation":"resolved","description":"From write_foundry_tests 152f0f2e, reproduced. endSeason decides between rolling the unpaid pot and sweeping it to the hook's owner by reading the storage flag deadManTriggered, but unlike every other state-changing entry point it never calls _latchDeadMan(). Once the 33-hour silence has passed, tradesFreely() is true and no points can be earned again (judge, _submit and appeal latch and revert FreeTrading), yet the flag is still false until someone calls triggerDeadManSwitch, withdrawPlea, authorizeSale or ruleObjection. An endSeason in that gap rolls the pot into the next season contrary to the function's documented rule, and the owner waits another 30 days for the sweep. No funds are lost: the next endSeason after a latch sweeps the whole pot (the board is empty, so paid is 0). Fix: call _latchDeadMan() at the top of endSeason.","line":584,"path":"src/PleaCourt.sol","reproduction":"test/scratch/EndSeasonLatch.t.sol: court bound to an ERC-20 IMD, 30 judges set; seller submitPlea('x',1e18) and no judge votes; imd.transfer(court, 5e18); vm.warp(+30 days) so tradesFreely()==true and deadManTriggered()==false; endSeason(). Expected: claimable(owner)==5e18 and prizePot()==0. Actual: claimable(owner)==0, prizePot()==5e18 (rolled into season 2) and deadManTriggered() still false.","severity":"low","snippet":"        if (deadManTriggered && pot > paid) {","title":"endSeason does not latch a due dead-man switch, so the pot rolls over for a whole season instead of being swept to the owner"}],"hash":"fb4b081831473ba276e40dfec461fee71e7a788bf1c86c2dafadc9574681e23c","nodeId":"2e3806a9-8ced-42c3-9d6a-9eb9179b56b9","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. The working tree is clean apart from that file.\n\n**Earlier findings: all settled.** Every one of the seven attached proofs now passes on the revised code, and I traced each author reproduction against the new source. The eight blocking findings (hung Cabal, mid-vote withdrawal, strike after season end, clock before the Cabal, withdraw-and-resubmit, ban only in submit, board promotion on strike, post-switch pot fee) are fixed. The advisory ones are fixed or documented as the author said, including the disputed points-at-approval item, which stays a documented limitation. The full suite passes (150 tests).\n\n**New findings this round:**\n\n1. **Medium, with proof.** The new recusal rule in `judge()` and the new hung-Cabal rule at `src/PleaCourt.sol:372` conflict. A judge who pleads is recused, so their plea can collect at most 29 votes, but the hung rule settles only at 30. A 29-vote split (15 approve / 14 deny) leaves the plea Pending forever: no further vote is possible, and the seller cannot withdraw it because scored pleas now revert `VotesCast`. The dead-man clock runs out 33 hours later and frees trading permanently. This is the earlier high finding re-opened for the narrower case of a judge who pleads, so it is rated medium. The proof test fails on this code and passes once the threshold counts only eligible judges.\n\n2. **Low.** `endSeason` reads the `deadManTriggered` flag without latching first. When the switch is due but not yet latched, it rolls the pot into the next season instead of sweeping it to the owner, delaying the sweep by 30 days. No funds are lost. This confirms the tester's lead.\n\n**Coverage.** All 31 listed entry points are answered, plus rows for `beforeSwap`, `beforeAddLiquidity`, and the settled earlier findings. Two rows point at the findings above; the rest hold.","treeHash":null,"usage":{"cachedInputTokens":723009,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":15697,"runtime":"claude","turns":14,"wallClockMs":288791}}],"verification":[{"checks":[{"durationMs":8215,"exitCode":0,"name":"build","output":"Compiling 117 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.10s\nCompiler run successful!\n","passed":true},{"durationMs":12550,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 665.59µs (766.83µs CPU time)\n\nRan 4 tests for test/PleaExternalCalls.t.sol:PleaExternalCallsTest\n[PASS] test_falseReturningBondTransferCannotMarkSaleObjected() (gas: 2683067)\n[PASS] test_reentrantClaimCannotWithdrawTheSameBondTwice() (gas: 3992963)\n[PASS] test_reentrantSellDuringIMDOutputIsRejectedAndOuterSaleSettles() (gas: 2480507)\n[PASS] test_rejectedClaimTransferRestoresCreditAndOtherUsersRemainIndependent() (gas: 4140917)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 75.51ms (6.97ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 401857, ~: 379574)\n[PASS] test_appealFlow() (gas: 3537269)\n[PASS] test_appealOfApprovedReverts() (gas: 1914784)\n[PASS] test_approvalAt17Votes() (gas: 1911404)\n[PASS] test_closeWindowAfterExpiry() (gas: 1919674)\n[PASS] test_denialAt17Votes() (gas: 2952953)\n[PASS] test_emptySeasonRollsPotOver() (gas: 163557)\n[PASS] test_judgeInputChecks() (gas: 511700)\n[PASS] test_leaderboardKeepsTop25() (gas: 46530490)\n[PASS] test_medianOfMixedScores() (gas: 1822996)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3730311)\n[PASS] test_objectionNeedsBond() (gas: 2347236)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2645688)\n[PASS] test_objectionUpheld() (gas: 4237249)\n[PASS] test_objectionWindow() (gas: 2286724)\n[PASS] test_onlyOneAppeal() (gas: 3615015)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3889360)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43417)\n[PASS] test_seasonSplitsPotProRata() (gas: 5828077)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3553656)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633600)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271848)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 85.78ms (98.59ms CPU time)\n\nRan 6 tests for test/LetterboxAdversarial.t.sol:LetterboxAdversarialTest\n[PASS] testFuzz_modifyingAnySignedFieldFails(uint8,uint128) (runs: 1000, μ: 393901, ~: 393368)\n[PASS] test_directPostCannotSpendASignedNonce() (gas: 538344)\n[PASS] test_domainSeparatesLetterboxesAndChains() (gas: 409519)\n[PASS] test_downstreamRevertPreservesSignatureForRetry() (gas: 735010)\n[PASS] test_malformedAndMalleableSignaturesCannotConsumeNonce() (gas: 506111)\n[PASS] test_signatureAtDeadlineAcceptedAndExpiredAttemptCannotBurnNonce() (gas: 349101)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 407.67ms (407.66ms CPU time)\n\nRan 25 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4090708)\n[PASS] test_boardCapacityIsFifty() (gas: 96221188)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3857430)\n[PASS] test_eligibleJudgesIsTheWholeCabalForOutsiders() (gas: 2972402)\n[PASS] test_endSeasonLatchesADueDeadManSwitchAndSweeps() (gas: 476291)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3145632)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3094810)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 377242)\n[PASS] test_judgeSellerPleaSettlesWhenAllEligibleJudgesVoted() (gas: 4584334)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1909012)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3681430)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 3029277)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 163210)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2643221)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2544247)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 4025286)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5998687)\n[PASS] test_strikePromotesTheBench() (gas: 52177171)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5474134)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4299219)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3859921)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2073825)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 393737)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 577.76ms (149.81ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken1AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2661499, ~: 2672327)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 18423507)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7513494)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2387513)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 20418428)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2468392)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 344062)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1834271)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2691370)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1998792)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 587.43ms (543.90ms CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256537)\n[PASS] test_postSignedByRelayer() (gas: 351738)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 594.70ms (1.37ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken0AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2668800, ~: 2679852)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 18437880)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7506513)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2394294)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 20452004)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2475173)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 337247)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1827348)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2704932)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1998794)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 594.75ms (551.76ms CPU time)\n\nRan 14 tests for test/PleaCourtRevision.t.sol:PleaCourtRevisionTest\n[PASS] testFuzz_judgeSellerSplitIsDeniedAtExactlyTheTwentyNinthVote(uint256,uint256) (runs: 1000, μ: 3193947, ~: 3191959)\n[PASS] testFuzz_settleNeverChargesMoreThanConsumed(uint128,uint128,uint128) (runs: 1000, μ: 2021206, ~: 2021490)\n[PASS] test_endSeasonLatchPaysTheBoardBeforeSweepingTheRest() (gas: 2398575)\n[PASS] test_partialFillReturnsTheUnconsumedReservationToTheWindow() (gas: 2207145)\n[PASS] test_partiallyScoredPleaCannotBeWithdrawnToDodgeADenial() (gas: 1726829)\n[PASS] test_rotatingTheJudgeSellerOffThePanelRestoresThirtyEligibleVotes() (gas: 3335176)\n[PASS] test_rulingAtTheLastSecondStillBansAndPrecedesThePrizeUnlock() (gas: 5564132)\n[PASS] test_settleRejectsUnknownSalesAndOverconsumptionAtomically() (gas: 2078729)\n[PASS] test_strikingAWinnerPromotesTheTwentySixthSellerIntoThePrize() (gas: 53125719)\n[PASS] test_struckSellerCannotSpendTheRestOfTheWindowNorPleadDuringTheBan() (gas: 3788825)\n[PASS] test_switchDueAtTheSeasonEndIsLatchedAndSweptByEndSeason() (gas: 477899)\n[PASS] test_switchDueOneSecondAfterTheSeasonEndRollsThePotOnce() (gas: 585705)\n[PASS] test_unendedSeasonPrizeCannotBeClaimed() (gas: 60781)\n[PASS] test_withdrawAndResubmitCannotEscapeTheOneRehearingRule() (gas: 3999108)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 617.92ms (1.27s CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176142, ~: 177794)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 13705031)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 14061056)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2296206)\n[PASS] test_sellAfterWindowReverts() (gas: 1985309)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2230621)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 2015452)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2312751)\n[PASS] test_sellMinOutReverts() (gas: 2176689)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1960423)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1920204)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2418039)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1952786)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 623.86ms (240.19ms CPU time)\n\nRan 16 tests for test/PleaCourtAdversarial.t.sol:PleaCourtAdversarialTest\n[PASS] testFuzz_prizeRoundingReservesBondsAndOldClaims(uint96) (runs: 1000, μ: 6547661, ~: 6552593)\n[PASS] testFuzz_seatRoundingAndFactThreshold(uint8,bool) (runs: 1000, μ: 3619702, ~: 3633650)\n[PASS] test_15To14SplitRemainsPendingUntilDeadManDeadline() (gas: 2859388)\n[PASS] test_15To15SplitIsAHungDenialAppealableOnceAndStopsTheClock() (gas: 4430557)\n[PASS] test_appealBoundaryPreservesRoundOneAndResetsVotingRights() (gas: 3565697)\n[PASS] test_courtAuthorityFollowsHookOwnershipImmediately() (gas: 247427)\n[PASS] test_evenMedianUsesBothMiddleValuesAndFloors() (gas: 1925493)\n[PASS] test_expiryCannotBeClosedAtLastSellableSecond() (gas: 2150204)\n[PASS] test_failedBondTransferLeavesSaleOpenToAnotherObjector() (gas: 2386246)\n[PASS] test_maximumTextAndUint128AmountAcceptedButNextAmountRejectedAtomically() (gas: 1384222)\n[PASS] test_objectionInclusiveBoundaryAndOneSecondTooLate() (gas: 2425376)\n[PASS] test_objectionNeedsDistinctJudgesAndCannotBeResolvedTwice() (gas: 3512492)\n[PASS] test_partialVotesAndNewPleasCannotExtendSilenceDeadline() (gas: 1869604)\n[PASS] test_rejectedVotesDoNotConsumeTheJudgeOrChangeTheTimer() (gas: 537436)\n[PASS] test_timeoutOneSecondBeforeAndAtBoundaryThenSingleClaim() (gas: 2485446)\n[PASS] test_zeroJudgeReplacementRollsBackAllMembershipChanges() (gas: 1807977)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 624.09ms (1.22s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken1InvariantTest\n[PASS]\nPleaHookToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.00s (3.93s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken0InvariantTest\n[PASS]\nPleaHookToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.07s (4.02s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken1InvariantTest\n[PASS]\nPleaHookGatedToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.32s (4.27s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken0InvariantTest\n[PASS]\nPleaHookGatedToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.59s (4.52s CPU time)\n\nRan 2 tests for test/PleaCourtInvariant.t.sol:PleaCourtInvariantTest\n[PASS]\nPleaCourtInvariantTest invariants:\n[PASS] invariant_IMDIsConservedAndAllLiabilitiesAreBacked\n[PASS] invariant_bondsResolveOnceAndBoardContainsEligibleActors\n[PASS] invariant_pendingCountsActivePleasAndVoteBoundsAgree\n PleaCourtInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+------------------+-------+---------+----------╮\n| Contract     | Selector         | Calls | Reverts | Discards |\n+==============================================================+\n| CourtHandler | advanceTime      | 988   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | appeal           | 996   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | authorize        | 949   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | authorizePartial | 941   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | claim            | 1014  | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | claimPrize       | 948   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | closeOrWithdraw  | 927   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | completeVerdict  | 1925  | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | donate           | 976   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | endSeason        | 975   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | judgeOne         | 996   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | object           | 885   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | reclaim          | 985   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | rule             | 965   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | setSeat          | 975   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | submit           | 939   | 0       | 0        |\n╰--------------+------------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesAppealTimeoutAndSeasonSettlement() (gas: 7131455)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 12.42s (12.38s CPU time)\n\nRan 16 test suites in 12.42s (34.19s CPU time): 158 tests passed, 0 failed, 0 skipped (158 total tests)\n","passed":true},{"durationMs":71,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":264,\"SECURITY.md\":172,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":834,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/LetterboxAdversarial.t.sol\":142,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtAdversarial.t.sol\":356,\"test/PleaCourtInvariant.t.sol\":194,\"test/PleaCourtRevision.t.sol\":339,\"test/PleaCourtRules.t.sol\":576,\"test/PleaExternalCalls.t.sol\":115,\"test/PleaHook.t.sol\":405,\"test/PleaHookAdversarial.t.sol\":245,\"test/PleaHookInvariant.t.sol\":134,\"test/README.md\":73,\"test/floor/OwnerChecks.sol\":21,\"test/helpers/CourtFixture.sol\":104,\"test/helpers/CourtHandler.sol\":357,\"test/helpers/HookHandler.sol\":258,\"test/helpers/OrderedHookFixture.sol\":78,\"test/mocks/AdversarialIMD.sol\":64,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"05421120e2138093285f13cae2df24b25f6139ede37f069c43bc9c5b369b6a1c","verifiedTreeHash":"2fd0bec42f1c3f3276102d81825e6c438c4853c7","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":4022,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.91s\nCompiler run successful!\n","passed":true},{"durationMs":184,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.53ms (518.09µs CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256515)\n[PASS] test_postSignedByRelayer() (gas: 351716)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 29.70ms (2.29ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176069, ~: 177787)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 31506396)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 31862421)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2257438)\n[PASS] test_sellAfterWindowReverts() (gas: 1946519)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2191831)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1976684)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2273983)\n[PASS] test_sellMinOutReverts() (gas: 2137921)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1921655)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1881436)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2379271)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1914018)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 32.28ms (94.40ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 399299, ~: 377107)\n[PASS] test_appealFlow() (gas: 3459713)\n[PASS] test_appealOfApprovedReverts() (gas: 1876016)\n[PASS] test_approvalAt17Votes() (gas: 1872614)\n[PASS] test_closeWindowAfterExpiry() (gas: 1880906)\n[PASS] test_denialAt17Votes() (gas: 2882664)\n[PASS] test_emptySeasonRollsPotOver() (gas: 160954)\n[PASS] test_judgeInputChecks() (gas: 509277)\n[PASS] test_leaderboardKeepsTop25() (gas: 45522522)\n[PASS] test_medianOfMixedScores() (gas: 1784206)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3691588)\n[PASS] test_objectionNeedsBond() (gas: 2308468)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2606943)\n[PASS] test_objectionUpheld() (gas: 4198552)\n[PASS] test_objectionWindow() (gas: 2247956)\n[PASS] test_onlyOneAppeal() (gas: 3537457)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3811824)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43395)\n[PASS] test_seasonSplitsPotProRata() (gas: 5709237)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3476098)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633578)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271826)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 32.30ms (83.26ms CPU time)\n\nRan 22 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4013150)\n[PASS] test_boardCapacityIsFifty() (gas: 94205208)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3779828)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3106596)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3022064)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 374736)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1908923)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3642552)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 2990487)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 160696)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2603455)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2505457)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 3983982)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5918548)\n[PASS] test_strikePromotesTheBench() (gas: 51128352)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5396598)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4258028)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3821153)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2035056)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 391292)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 56.38ms (116.79ms CPU time)\n\nRan 5 test suites in 57.64ms (153.19ms CPU time): 89 tests passed, 0 failed, 0 skipped (89 total tests)\n","passed":true},{"durationMs":71,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":263,\"SECURITY.md\":157,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":826,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtRules.t.sol\":502,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3744,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/PleaCourt.sol:555: PleaCourt.endSeason() (src/PleaCourt.sol#555-591) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:594: PleaCourt.claimPrize(uint256) (src/PleaCourt.sol#594-603) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:770: PleaCourt.prizeUnlockAt(uint256) (src/PleaCourt.sol#770-773) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:606: PleaCourt.claim() (src/PleaCourt.sol#606-613) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/PleaCourt.sol:562: PleaCourt.endSeason().totalPoints (src/PleaCourt.sol#562) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:567: PleaCourt.endSeason().paid (src/PleaCourt.sol#567) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:566: PleaCourt.endSeason().winners (src/PleaCourt.sol#566) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:625: PleaCourt._award(address,uint256).minIdx (src/PleaCourt.sol#625) is a local variable never initialized\n[medium/medium] unused-return at src/PleaHook.sol:173: PleaHook.unlockCallback(bytes) (src/PleaHook.sol#173-200) ignores return value by poolManager.settle() (src/PleaHook.sol#196)\n[low/medium] missing-zero-check at src/PleaCourt.sol:224: PleaCourt.constructor(address).hook_ (src/PleaCourt.sol#224) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:80: PleaHook.constructor(IPoolManager,address,address,address).factory_ (src/PleaHook.sol#80) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:80: PleaHook.constructor(IPoolManager,address,address,address).token_ (src/PleaHook.sol#80) lacks a zero-check on :\n[low/medium] reentrancy-events at src/PleaHook.sol:112: Reentrancy in PleaHook.beforeInitialize(address,PoolKey,uint160) (src/PleaHook.sol#112-125):\n[low/medium] reentrancy-events at src/Letterbox.sol:32: Reentrancy in Letterbox.post(string,uint256) (src/Letterbox.sol#32-35):\n[low/medium] reentrancy-events at src/Letterbox.sol:38: Reentrancy in Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#38-53):\n[low/medium] timestamp at src/PleaCourt.sol:703: PleaCourt.deadManDeadline() (src/PleaCourt.sol#703-707) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:620: PleaCourt._award(address,uint256) (src/PleaCourt.sol#620-641) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:645: PleaCourt._strike(address,uint256) (src/PleaCourt.sol#645-663) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:500: PleaCourt.ruleObjection(uint256,bool) (src/PleaCourt.sol#500-530) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:770: PleaCourt.prizeUnlockAt(uint256) (src/PleaCourt.sol#770-773) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:676: PleaCourt._latchDeadMan() (src/PleaCourt.sol#676-681) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:455: PleaCourt.closeWindow(uint256) (src/PleaCourt.sol#455-460) uses timestamp for comparisons\n[low/medium] timestamp at src/Letterbox.sol:38: Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#38-53) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:745: PleaCourt.saleWindow(address) (src/PleaCourt.sol#745-751) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:555: PleaCourt.endSeason() (src/PleaCourt.sol#555-591) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:242: PleaCourt.authorizeSale(address,uint256) (src/PleaCourt.sol#242-261) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:314: PleaCourt._submit(address,string,uint256,bool) (src/PleaCourt.sol#314-343) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:404: PleaCourt.appeal(uint256) (src/PleaCourt.sol#404-422) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:594: PleaCourt.claimPrize(uint256) (src/PleaCourt.sol#594-603) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:710: PleaCourt.deadManTimer() (src/PleaCourt.sol#710-714) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:775: PleaCourt._sorted(uint256) (src/PleaCourt.sol#775-792) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:533: PleaCourt.reclaimObjectionBond(uint256) (src/PleaCourt.sol#533-541) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:472: PleaCourt.object(uint256) (src/PleaCourt.sol#472-494) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:696: PleaCourt.tradesFreely() (src/PleaCourt.sol#696-700) uses timestamp for comparisons","passed":true},{"durationMs":703,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/PleaCourt.sol:279: Centralization Risk (5 places)\n[low] costly-loop at src/PleaCourt.sol:282: Costly operations inside loop (5 places)\n[low] internal-function-used-once at src/HookFlags.sol:26: Internal Function Used Only Once\n[low] large-numeric-literal at src/PLEAToken.sol:11: Large Numeric Literal (2 places)\n[low] require-revert-in-loop at src/PleaCourt.sol:286: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/PleaCourt.sol:235: Address State Variable Set Without Checks\n[low] unchecked-return at src/PleaHook.sol:196: Unchecked Return\n[low] uninitialized-local-variable at src/PleaCourt.sol:566: Uninitialized Local Variable\n[low] unused-error at src/PleaHook.sol:47: Unused Error","passed":true},{"durationMs":2260,"exitCode":0,"name":"proof dd7e9df2facb","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.60s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-20462e99/Proof_dd7e9df2facb.t.sol:DeadlockTest\n[PASS] test_splitCabalCannotReachAVerdictAndTheDeadManFires() (gas: 2892789)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.12ms (858.07µs CPU time)\n\nRan 1 test suite in 4.09ms (3.12ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1510,"exitCode":0,"name":"proof 33477e169a57","output":"2026-10-30T11:56:09.126037Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-vBaFgH/repo/test/imd-proof-20462e99/Proof_dd7e9df2facb.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 905.66ms\nCompiler run successful!\n\nRan 1 test for test/imd-proof-8948bdc0/Proof_33477e169a57.t.sol:WithdrawDeadManTest\n[PASS] test_withdrawBeforeQuorumTripsDeadManOnActiveCabal() (gas: 10535654)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.96ms (2.41ms CPU time)\n\nRan 1 test suite in 5.82ms (4.96ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":2407,"exitCode":0,"name":"proof 19f1179b7c87","output":"2026-10-30T11:56:10.741931Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-vBaFgH/repo/test/imd-proof-8948bdc0/Proof_33477e169a57.t.sol\": No such file or directory (os error 2)\nCompiling 25 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.71s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-aee847dc/Proof_19f1179b7c87.t.sol:StrikeAfterSeasonEndTest\n[PASS] test_upheldObjectionDoesNotVoidPrizeAfterSeasonEnd() (gas: 3822448)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.74ms (876.85µs CPU time)\n\nRan 1 test suite in 10.28ms (9.74ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1f98eb30ea852f25718869bd467dcf9e0c9b4c8ddc0b92bbf4a33b0d581556b4","verifiedTreeHash":"0166102283b0eb3929c6eaf7630f8d1921636c94","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":6853,"exitCode":0,"name":"build","output":"Compiling 115 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.73s\nCompiler run successful!\n","passed":true},{"durationMs":11991,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 603.60µs (590.33µs CPU time)\n\nRan 4 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35499)\n[PASS] test_postDirect() (gas: 249467)\n[PASS] test_postSignedByRelayer() (gas: 344711)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 15.84ms (755.23µs CPU time)\n\nRan 4 tests for test/PleaExternalCalls.t.sol:PleaExternalCallsTest\n[PASS] test_falseReturningBondTransferCannotMarkSaleObjected() (gas: 2597246)\n[PASS] test_reentrantClaimCannotWithdrawTheSameBondTwice() (gas: 3750088)\n[PASS] test_reentrantSellDuringIMDOutputIsRejectedAndOuterSaleSettles() (gas: 2395310)\n[PASS] test_rejectedClaimTransferRestoresCreditAndOtherUsersRemainIndependent() (gas: 3870832)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 21.66ms (4.88ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 391651, ~: 369482)\n[PASS] test_appealFlow() (gas: 3391487)\n[PASS] test_appealOfApprovedReverts() (gas: 1860981)\n[PASS] test_approvalAt17Votes() (gas: 1857496)\n[PASS] test_closeWindowAfterExpiry() (gas: 1866050)\n[PASS] test_denialAt17Votes() (gas: 2821293)\n[PASS] test_emptySeasonRollsPotOver() (gas: 136253)\n[PASS] test_judgeInputChecks() (gas: 496112)\n[PASS] test_leaderboardKeepsTop25() (gas: 45063353)\n[PASS] test_medianOfMixedScores() (gas: 1769020)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3489956)\n[PASS] test_objectionNeedsBond() (gas: 2264112)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2557951)\n[PASS] test_objectionUpheld() (gas: 3970672)\n[PASS] test_objectionWindow() (gas: 2201270)\n[PASS] test_onlyOneAppeal() (gas: 3395067)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3752730)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43285)\n[PASS] test_seasonSplitsPotProRata() (gas: 5477374)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3404108)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633792)\n[PASS] test_setSeat() (gas: 119780)\n[PASS] test_submitEmitsAndStores() (gas: 264856)\n[PASS] test_submitInvalidInputsRevert() (gas: 344437)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 244619)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 91.22ms (81.50ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176597, ~: 177828)\n[PASS] test_buyExactOutputOpen() (gas: 165490)\n[PASS] test_buyIsAlwaysOpen() (gas: 163888)\n[PASS] test_callbacksRefuseNonManager() (gas: 153140)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1988104)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 378623)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256687)\n[PASS] test_initialize_fromStrangerReverts() (gas: 20622230)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 20978232)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 311248)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51835)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51469)\n[PASS] test_potFeeFundsTheCourt() (gas: 2213317)\n[PASS] test_sellAfterWindowReverts() (gas: 1929096)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2147404)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1959568)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67745)\n[PASS] test_sellInWindow() (gas: 2227685)\n[PASS] test_sellMinOutReverts() (gas: 2109423)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1904539)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1866560)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67272)\n[PASS] test_sellWhilePendingReverts() (gas: 318498)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2328352)\n[PASS] test_sellWithoutPleaReverts() (gas: 103804)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430069)\n[PASS] test_verdictRestartsTheTimer() (gas: 1850370)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 148.84ms (131.37ms CPU time)\n\nRan 6 tests for test/LetterboxAdversarial.t.sol:LetterboxAdversarialTest\n[PASS] testFuzz_modifyingAnySignedFieldFails(uint8,uint128) (runs: 1000, μ: 386574, ~: 386018)\n[PASS] test_directPostCannotSpendASignedNonce() (gas: 522810)\n[PASS] test_domainSeparatesLetterboxesAndChains() (gas: 402787)\n[PASS] test_downstreamRevertPreservesSignatureForRetry() (gas: 712622)\n[PASS] test_malformedAndMalleableSignaturesCannotConsumeNonce() (gas: 499511)\n[PASS] test_signatureAtDeadlineAcceptedAndExpiredAttemptCannotBurnNonce() (gas: 342030)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 429.19ms (405.76ms CPU time)\n\nRan 15 tests for test/PleaCourtAdversarial.t.sol:PleaCourtAdversarialTest\n[PASS] testFuzz_prizeRoundingReservesBondsAndOldClaims(uint96) (runs: 1000, μ: 6084524, ~: 6086661)\n[PASS] testFuzz_seatRoundingAndFactThreshold(uint8,bool) (runs: 1000, μ: 3466581, ~: 3484014)\n[PASS] test_15To15SplitRemainsPendingUntilDeadManDeadline() (gas: 2811811)\n[PASS] test_appealBoundaryPreservesRoundOneAndResetsVotingRights() (gas: 3419525)\n[PASS] test_courtAuthorityFollowsHookOwnershipImmediately() (gas: 247406)\n[PASS] test_evenMedianUsesBothMiddleValuesAndFloors() (gas: 1868769)\n[PASS] test_expiryCannotBeClosedAtLastSellableSecond() (gas: 2064402)\n[PASS] test_failedBondTransferLeavesSaleOpenToAnotherObjector() (gas: 2300435)\n[PASS] test_maximumTextAndUint128AmountAcceptedButNextAmountRejectedAtomically() (gas: 1375019)\n[PASS] test_objectionInclusiveBoundaryAndOneSecondTooLate() (gas: 2308105)\n[PASS] test_objectionNeedsDistinctJudgesAndCannotBeResolvedTwice() (gas: 3258029)\n[PASS] test_partialVotesAndNewPleasCannotExtendSilenceDeadline() (gas: 1809461)\n[PASS] test_rejectedVotesDoNotConsumeTheJudgeOrChangeTheTimer() (gas: 520952)\n[PASS] test_timeoutOneSecondBeforeAndAtBoundaryThenSingleClaim() (gas: 2402136)\n[PASS] test_zeroJudgeReplacementRollsBackAllMembershipChanges() (gas: 1805427)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 480.30ms (944.09ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken0AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2551822, ~: 2562963)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 21869625)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7121029)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2309169)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 23816054)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2377978)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 337201)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1827324)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2608020)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1945089)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 488.41ms (510.25ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken1AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2544718, ~: 2555466)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 21844642)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7128010)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2302388)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 23771868)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2371197)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 344016)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1834247)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2594458)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1945087)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 488.41ms (495.99ms CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken1InvariantTest\n[PASS]\nPleaHookToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.79s (3.77s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken0InvariantTest\n[PASS]\nPleaHookToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.85s (3.83s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken1InvariantTest\n[PASS]\nPleaHookGatedToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.24s (4.21s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken0InvariantTest\n[PASS]\nPleaHookGatedToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.28s (4.24s CPU time)\n\nRan 2 tests for test/PleaCourtInvariant.t.sol:PleaCourtInvariantTest\n[PASS]\nPleaCourtInvariantTest invariants:\n[PASS] invariant_IMDIsConservedAndAllLiabilitiesAreBacked\n[PASS] invariant_bondsResolveOnceAndBoardContainsEligibleActors\n[PASS] invariant_pendingCountsActivePleasAndVoteBoundsAgree\n PleaCourtInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+-----------------+-------+---------+----------╮\n| Contract     | Selector        | Calls | Reverts | Discards |\n+=============================================================+\n| CourtHandler | advanceTime     | 1086  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | appeal          | 1097  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | authorize       | 1140  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | claim           | 1076  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | closeOrWithdraw | 1086  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | completeVerdict | 2173  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | donate          | 1091  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | endSeason       | 1032  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | judgeOne        | 1064  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | object          | 1135  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | reclaim         | 1069  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | rule            | 1117  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | setSeat         | 1116  | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| CourtHandler | submit          | 1102  | 0       | 0        |\n╰--------------+-----------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesAppealTimeoutAndSeasonSettlement() (gas: 5759356)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.87s (11.85s CPU time)\n\nRan 14 test suites in 11.87s (30.19s CPU time): 117 tests passed, 0 failed, 0 skipped (117 total tests)\n","passed":true},{"durationMs":79,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":214,\"SECURITY.md\":107,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":58,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":676,\"src/PleaHook.sol\":262,\"src/interfaces/IPleaCourt.sol\":53,\"test/Letterbox.t.sol\":73,\"test/LetterboxAdversarial.t.sol\":142,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":491,\"test/PleaCourtAdversarial.t.sol\":300,\"test/PleaCourtInvariant.t.sol\":169,\"test/PleaExternalCalls.t.sol\":115,\"test/PleaHook.t.sol\":405,\"test/PleaHookAdversarial.t.sol\":245,\"test/PleaHookInvariant.t.sol\":134,\"test/README.md\":61,\"test/floor/OwnerChecks.sol\":21,\"test/helpers/CourtFixture.sol\":92,\"test/helpers/CourtHandler.sol\":265,\"test/helpers/HookHandler.sol\":258,\"test/helpers/OrderedHookFixture.sol\":78,\"test/mocks/AdversarialIMD.sol\":64,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"336dd32d5da547112346f75fc46dc15c93c548e70961c2f6b79a65ea3de0967d","verifiedTreeHash":"531e067ffc1778bfb704be332372babb509628d4","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":8055,"exitCode":0,"name":"build","output":"Compiling 117 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.94s\nCompiler run successful!\n","passed":true},{"durationMs":12360,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 601.46µs (593.18µs CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256515)\n[PASS] test_postSignedByRelayer() (gas: 351716)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.59ms (781.12µs CPU time)\n\nRan 4 tests for test/PleaExternalCalls.t.sol:PleaExternalCallsTest\n[PASS] test_falseReturningBondTransferCannotMarkSaleObjected() (gas: 2644299)\n[PASS] test_reentrantClaimCannotWithdrawTheSameBondTwice() (gas: 3954227)\n[PASS] test_reentrantSellDuringIMDOutputIsRejectedAndOuterSaleSettles() (gas: 2441739)\n[PASS] test_rejectedClaimTransferRestoresCreditAndOtherUsersRemainIndependent() (gas: 4099640)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.85ms (4.97ms CPU time)\n\nRan 22 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4013150)\n[PASS] test_boardCapacityIsFifty() (gas: 94205208)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3779828)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3106596)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3022064)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 374736)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1908923)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3642552)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 2990487)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 160696)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2603455)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2505457)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 3983982)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5918548)\n[PASS] test_strikePromotesTheBench() (gas: 51128352)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5396598)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4258028)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3821153)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2035056)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 391292)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 182.16ms (174.07ms CPU time)\n\nRan 6 tests for test/LetterboxAdversarial.t.sol:LetterboxAdversarialTest\n[PASS] testFuzz_modifyingAnySignedFieldFails(uint8,uint128) (runs: 1000, μ: 393851, ~: 393324)\n[PASS] test_directPostCannotSpendASignedNonce() (gas: 538344)\n[PASS] test_domainSeparatesLetterboxesAndChains() (gas: 409519)\n[PASS] test_downstreamRevertPreservesSignatureForRetry() (gas: 734988)\n[PASS] test_malformedAndMalleableSignaturesCannotConsumeNonce() (gas: 506111)\n[PASS] test_signatureAtDeadlineAcceptedAndExpiredAttemptCannotBurnNonce() (gas: 349079)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 426.91ms (428.12ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken1AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2622778, ~: 2633510)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 23152664)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7397190)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2348745)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 25108783)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2429580)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 344062)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1834271)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2652580)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1960024)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 482.81ms (511.04ms CPU time)\n\nRan 9 tests for test/PleaCourtRevision.t.sol:PleaCourtRevisionTest\n[PASS] testFuzz_settleNeverChargesMoreThanConsumed(uint128,uint128,uint128) (runs: 1000, μ: 1982391, ~: 1982690)\n[PASS] test_partialFillReturnsTheUnconsumedReservationToTheWindow() (gas: 2168332)\n[PASS] test_partiallyScoredPleaCannotBeWithdrawnToDodgeADenial() (gas: 1688071)\n[PASS] test_rulingAtTheLastSecondStillBansAndPrecedesThePrizeUnlock() (gas: 5483926)\n[PASS] test_settleRejectsUnknownSalesAndOverconsumptionAtomically() (gas: 2039894)\n[PASS] test_strikingAWinnerPromotesTheTwentySixthSellerIntoThePrize() (gas: 52115669)\n[PASS] test_struckSellerCannotSpendTheRestOfTheWindowNorPleadDuringTheBan() (gas: 3750057)\n[PASS] test_unendedSeasonPrizeCannotBeClaimed() (gas: 60736)\n[PASS] test_withdrawAndResubmitCannotEscapeTheOneRehearingRule() (gas: 3921462)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 539.99ms (595.56ms CPU time)\n\nRan 10 tests for test/PleaHookAdversarial.t.sol:PleaHookToken0AdversarialTest\n[PASS] testFuzz_feeAndSplitSalesConserveBothCurrencies(uint96,uint16) (runs: 1000, μ: 2630292, ~: 2641040)\n[PASS] test_buyOnFreshTokenOnlyPoolNeedsNoExistingIMD() (gas: 23178854)\n[PASS] test_buyUnaffectedByPendingDeniedExpiredAndBannedSeller() (gas: 7390209)\n[PASS] test_donationsCannotSubsidizeUnauthorizedSeller() (gas: 2355526)\n[PASS] test_emptyPoolCannotConsumeApprovalWhenMinimumOutputIsPositive() (gas: 25154176)\n[PASS] test_insufficientAllowanceRollsBackAuthorizationAndCanBeRetried() (gas: 2436361)\n[PASS] test_ownerTransferMovesCourtAndHookPermissionsTogether() (gas: 337247)\n[PASS] test_revokedLPStillCollectsAndWithdrawsButCannotIncreasePosition() (gas: 1827348)\n[PASS] test_slippageFailureRollsBackPoolFeeAllowanceAndCourt() (gas: 2666142)\n[PASS] test_spoofedHookDataCannotSellExactInputOrExactOutput() (gas: 1960026)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 542.24ms (564.97ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176292, ~: 177795)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 31506396)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 31862421)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2257438)\n[PASS] test_sellAfterWindowReverts() (gas: 1946519)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2191831)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1976684)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2273983)\n[PASS] test_sellMinOutReverts() (gas: 2137921)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1921655)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1881436)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2379271)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1914018)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 549.06ms (99.35ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 399380, ~: 377107)\n[PASS] test_appealFlow() (gas: 3459713)\n[PASS] test_appealOfApprovedReverts() (gas: 1876016)\n[PASS] test_approvalAt17Votes() (gas: 1872614)\n[PASS] test_closeWindowAfterExpiry() (gas: 1880906)\n[PASS] test_denialAt17Votes() (gas: 2882664)\n[PASS] test_emptySeasonRollsPotOver() (gas: 160954)\n[PASS] test_judgeInputChecks() (gas: 509277)\n[PASS] test_leaderboardKeepsTop25() (gas: 45522522)\n[PASS] test_medianOfMixedScores() (gas: 1784206)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3691588)\n[PASS] test_objectionNeedsBond() (gas: 2308468)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2606943)\n[PASS] test_objectionUpheld() (gas: 4198552)\n[PASS] test_objectionWindow() (gas: 2247956)\n[PASS] test_onlyOneAppeal() (gas: 3537457)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3811824)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43395)\n[PASS] test_seasonSplitsPotProRata() (gas: 5709237)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3476098)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633578)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271826)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 549.06ms (616.93ms CPU time)\n\nRan 16 tests for test/PleaCourtAdversarial.t.sol:PleaCourtAdversarialTest\n[PASS] testFuzz_prizeRoundingReservesBondsAndOldClaims(uint96) (runs: 1000, μ: 6428964, ~: 6433865)\n[PASS] testFuzz_seatRoundingAndFactThreshold(uint8,bool) (runs: 1000, μ: 3540491, ~: 3556092)\n[PASS] test_15To14SplitRemainsPendingUntilDeadManDeadline() (gas: 2789121)\n[PASS] test_15To15SplitIsAHungDenialAppealableOnceAndStopsTheClock() (gas: 4319077)\n[PASS] test_appealBoundaryPreservesRoundOneAndResetsVotingRights() (gas: 3488097)\n[PASS] test_courtAuthorityFollowsHookOwnershipImmediately() (gas: 247427)\n[PASS] test_evenMedianUsesBothMiddleValuesAndFloors() (gas: 1884280)\n[PASS] test_expiryCannotBeClosedAtLastSellableSecond() (gas: 2111392)\n[PASS] test_failedBondTransferLeavesSaleOpenToAnotherObjector() (gas: 2347478)\n[PASS] test_maximumTextAndUint128AmountAcceptedButNextAmountRejectedAtomically() (gas: 1384200)\n[PASS] test_objectionInclusiveBoundaryAndOneSecondTooLate() (gas: 2386608)\n[PASS] test_objectionNeedsDistinctJudgesAndCannotBeResolvedTwice() (gas: 3473813)\n[PASS] test_partialVotesAndNewPleasCannotExtendSilenceDeadline() (gas: 1830836)\n[PASS] test_rejectedVotesDoNotConsumeTheJudgeOrChangeTheTimer() (gas: 534971)\n[PASS] test_timeoutOneSecondBeforeAndAtBoundaryThenSingleClaim() (gas: 2446550)\n[PASS] test_zeroJudgeReplacementRollsBackAllMembershipChanges() (gas: 1807955)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 549.27ms (1.05s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken1InvariantTest\n[PASS]\nPleaHookToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.78s (3.76s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookToken0InvariantTest\n[PASS]\nPleaHookToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1529  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1548  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1473  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1534  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1452  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1460  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1508  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1429  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1486  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1479  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | triggerDeadMan     | 1486  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.02s (4.00s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken1InvariantTest\n[PASS]\nPleaHookGatedToken1InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken1InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.19s (4.17s CPU time)\n\nRan 1 test for test/PleaHookInvariant.t.sol:PleaHookGatedToken0InvariantTest\n[PASS]\nPleaHookGatedToken0InvariantTest invariants:\n[PASS] invariant_fixedSupplyAndBothTokenBalancesAreConserved\n[PASS] invariant_onlyExplicitDonationsRemainInHookAndAllFeesReachCourt\n PleaHookGatedToken0InvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| HookHandler | advanceTime        | 1639  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | buy                | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | collectFactoryFees | 1691  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | donateToHook       | 1584  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | preparePlea        | 1650  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | rejectedSlippage   | 1667  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | routerSell         | 1681  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | sell               | 1565  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | setFee             | 1611  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| HookHandler | transfer           | 1605  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.41s (4.39s CPU time)\n\nRan 2 tests for test/PleaCourtInvariant.t.sol:PleaCourtInvariantTest\n[PASS]\nPleaCourtInvariantTest invariants:\n[PASS] invariant_IMDIsConservedAndAllLiabilitiesAreBacked\n[PASS] invariant_bondsResolveOnceAndBoardContainsEligibleActors\n[PASS] invariant_pendingCountsActivePleasAndVoteBoundsAgree\n PleaCourtInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+------------------+-------+---------+----------╮\n| Contract     | Selector         | Calls | Reverts | Discards |\n+==============================================================+\n| CourtHandler | advanceTime      | 988   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | appeal           | 996   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | authorize        | 949   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | authorizePartial | 941   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | claim            | 1014  | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | claimPrize       | 948   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | closeOrWithdraw  | 927   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | completeVerdict  | 1925  | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | donate           | 976   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | endSeason        | 975   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | judgeOne         | 996   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | object           | 885   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | reclaim          | 985   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | rule             | 965   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | setSeat          | 975   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| CourtHandler | submit           | 939   | 0       | 0        |\n╰--------------+------------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesAppealTimeoutAndSeasonSettlement() (gas: 7110836)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 12.23s (12.21s CPU time)\n\nRan 16 test suites in 12.23s (32.48s CPU time): 150 tests passed, 0 failed, 0 skipped (150 total tests)\n","passed":true},{"durationMs":72,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":263,\"SECURITY.md\":157,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":826,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/LetterboxAdversarial.t.sol\":142,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtAdversarial.t.sol\":356,\"test/PleaCourtInvariant.t.sol\":194,\"test/PleaCourtRevision.t.sol\":227,\"test/PleaCourtRules.t.sol\":502,\"test/PleaExternalCalls.t.sol\":115,\"test/PleaHook.t.sol\":405,\"test/PleaHookAdversarial.t.sol\":245,\"test/PleaHookInvariant.t.sol\":134,\"test/README.md\":70,\"test/floor/OwnerChecks.sol\":21,\"test/helpers/CourtFixture.sol\":104,\"test/helpers/CourtHandler.sol\":357,\"test/helpers/HookHandler.sol\":258,\"test/helpers/OrderedHookFixture.sol\":78,\"test/mocks/AdversarialIMD.sol\":64,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"418aa5337c99b1edf58f05eed2694bdf34644b5a0fa3c40adf55b1050c236788","verifiedTreeHash":"7f6d1efc34ca95d54b3a0547a43d071fa6da9677","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":3912,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.81s\nCompiler run successful!\n","passed":true},{"durationMs":188,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.29ms (1.80ms CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256515)\n[PASS] test_postSignedByRelayer() (gas: 351716)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 5.47ms (1.39ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 399299, ~: 377107)\n[PASS] test_appealFlow() (gas: 3459713)\n[PASS] test_appealOfApprovedReverts() (gas: 1876016)\n[PASS] test_approvalAt17Votes() (gas: 1872614)\n[PASS] test_closeWindowAfterExpiry() (gas: 1880906)\n[PASS] test_denialAt17Votes() (gas: 2882664)\n[PASS] test_emptySeasonRollsPotOver() (gas: 160954)\n[PASS] test_judgeInputChecks() (gas: 509277)\n[PASS] test_leaderboardKeepsTop25() (gas: 45522522)\n[PASS] test_medianOfMixedScores() (gas: 1784206)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3691588)\n[PASS] test_objectionNeedsBond() (gas: 2308468)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2606943)\n[PASS] test_objectionUpheld() (gas: 4198552)\n[PASS] test_objectionWindow() (gas: 2247956)\n[PASS] test_onlyOneAppeal() (gas: 3537457)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3811824)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43395)\n[PASS] test_seasonSplitsPotProRata() (gas: 5709237)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3476098)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633578)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271826)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 25.23ms (71.77ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176069, ~: 177787)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 31506396)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 31862421)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2257438)\n[PASS] test_sellAfterWindowReverts() (gas: 1946519)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2191831)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1976684)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2273983)\n[PASS] test_sellMinOutReverts() (gas: 2137921)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1921655)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1881436)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2379271)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1914018)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 32.05ms (84.15ms CPU time)\n\nRan 22 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4013150)\n[PASS] test_boardCapacityIsFifty() (gas: 94205208)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3779828)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3106596)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3022064)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 374736)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1908923)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3642552)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 2990487)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 160696)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2603455)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2505457)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 3983982)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5918548)\n[PASS] test_strikePromotesTheBench() (gas: 51128352)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5396598)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4258028)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3821153)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2035056)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 391292)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 60.97ms (121.65ms CPU time)\n\nRan 5 test suites in 62.32ms (125.00ms CPU time): 89 tests passed, 0 failed, 0 skipped (89 total tests)\n","passed":true},{"durationMs":71,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":263,\"SECURITY.md\":157,\"foundry.toml\":19,\"launch.json\":30,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":826,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtRules.t.sol\":502,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4d761f4d0f282c9b2b9c38fe5f5245fbc1578a5017d7a7101a51fde824073b98","verifiedTreeHash":"bf0220af620af48b747b86a78bde664b4d698115","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":3941,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.83s\nCompiler run successful!\n","passed":true},{"durationMs":178,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 660.72µs (604.74µs CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256537)\n[PASS] test_postSignedByRelayer() (gas: 351738)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 11.05ms (2.16ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176125, ~: 177795)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 13705031)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 14061056)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2296206)\n[PASS] test_sellAfterWindowReverts() (gas: 1985309)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2230621)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 2015452)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2312751)\n[PASS] test_sellMinOutReverts() (gas: 2176689)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1960423)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1920204)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2418039)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1952786)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 24.84ms (57.04ms CPU time)\n\nRan 25 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4090708)\n[PASS] test_boardCapacityIsFifty() (gas: 96221188)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3857430)\n[PASS] test_eligibleJudgesIsTheWholeCabalForOutsiders() (gas: 2972402)\n[PASS] test_endSeasonLatchesADueDeadManSwitchAndSweeps() (gas: 476291)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3145632)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3094810)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 377242)\n[PASS] test_judgeSellerPleaSettlesWhenAllEligibleJudgesVoted() (gas: 4584334)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1909012)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3681430)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 3029277)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 163210)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2643221)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2544247)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 4025286)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5998687)\n[PASS] test_strikePromotesTheBench() (gas: 52177171)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5474134)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4299219)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3859921)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2073825)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 393737)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 50.41ms (121.98ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 402013, ~: 379574)\n[PASS] test_appealFlow() (gas: 3537269)\n[PASS] test_appealOfApprovedReverts() (gas: 1914784)\n[PASS] test_approvalAt17Votes() (gas: 1911404)\n[PASS] test_closeWindowAfterExpiry() (gas: 1919674)\n[PASS] test_denialAt17Votes() (gas: 2952953)\n[PASS] test_emptySeasonRollsPotOver() (gas: 163557)\n[PASS] test_judgeInputChecks() (gas: 511700)\n[PASS] test_leaderboardKeepsTop25() (gas: 46530490)\n[PASS] test_medianOfMixedScores() (gas: 1822996)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3730311)\n[PASS] test_objectionNeedsBond() (gas: 2347236)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2645688)\n[PASS] test_objectionUpheld() (gas: 4237249)\n[PASS] test_objectionWindow() (gas: 2286724)\n[PASS] test_onlyOneAppeal() (gas: 3615015)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3889360)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43417)\n[PASS] test_seasonSplitsPotProRata() (gas: 5828077)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3553656)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633600)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271848)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 50.50ms (92.26ms CPU time)\n\nRan 5 test suites in 51.80ms (137.46ms CPU time): 92 tests passed, 0 failed, 0 skipped (92 total tests)\n","passed":true},{"durationMs":75,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":264,\"SECURITY.md\":172,\"foundry.toml\":19,\"launch.json\":30,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":834,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtRules.t.sol\":576,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"85b55c9a679dfe7d3ac0dbb7aa0ab0c16c6226b55276c0104f2267ea11c4d100","verifiedTreeHash":"2eb4814107ba5fcd6c637c25b45e0b6ca8c6cc0f","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":3231,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.13s\nCompiler run successful!\n","passed":true},{"durationMs":162,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.10ms (686.89µs CPU time)\n\nRan 4 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35499)\n[PASS] test_postDirect() (gas: 249467)\n[PASS] test_postSignedByRelayer() (gas: 344711)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.73ms (1.65ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 391746, ~: 369482)\n[PASS] test_appealFlow() (gas: 3391487)\n[PASS] test_appealOfApprovedReverts() (gas: 1860981)\n[PASS] test_approvalAt17Votes() (gas: 1857496)\n[PASS] test_closeWindowAfterExpiry() (gas: 1866050)\n[PASS] test_denialAt17Votes() (gas: 2821293)\n[PASS] test_emptySeasonRollsPotOver() (gas: 136253)\n[PASS] test_judgeInputChecks() (gas: 496112)\n[PASS] test_leaderboardKeepsTop25() (gas: 45063353)\n[PASS] test_medianOfMixedScores() (gas: 1769020)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3489956)\n[PASS] test_objectionNeedsBond() (gas: 2264112)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2557951)\n[PASS] test_objectionUpheld() (gas: 3970672)\n[PASS] test_objectionWindow() (gas: 2201270)\n[PASS] test_onlyOneAppeal() (gas: 3395067)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3752730)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43285)\n[PASS] test_seasonSplitsPotProRata() (gas: 5477374)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3404108)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633792)\n[PASS] test_setSeat() (gas: 119780)\n[PASS] test_submitEmitsAndStores() (gas: 264856)\n[PASS] test_submitInvalidInputsRevert() (gas: 344437)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 244619)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 31.60ms (67.42ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176910, ~: 177829)\n[PASS] test_buyExactOutputOpen() (gas: 165490)\n[PASS] test_buyIsAlwaysOpen() (gas: 163888)\n[PASS] test_callbacksRefuseNonManager() (gas: 153140)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1988104)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 378623)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256687)\n[PASS] test_initialize_fromStrangerReverts() (gas: 20622230)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 20978232)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 311248)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51835)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51469)\n[PASS] test_potFeeFundsTheCourt() (gas: 2213317)\n[PASS] test_sellAfterWindowReverts() (gas: 1929096)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2147404)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1959568)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67745)\n[PASS] test_sellInWindow() (gas: 2227685)\n[PASS] test_sellMinOutReverts() (gas: 2109423)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1904539)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1866560)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67272)\n[PASS] test_sellWhilePendingReverts() (gas: 318498)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2328352)\n[PASS] test_sellWithoutPleaReverts() (gas: 103804)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430069)\n[PASS] test_verdictRestartsTheTimer() (gas: 1850370)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 31.66ms (84.71ms CPU time)\n\nRan 4 test suites in 32.86ms (84.09ms CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":214,\"SECURITY.md\":107,\"foundry.toml\":19,\"launch.json\":30,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":58,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":676,\"src/PleaHook.sol\":262,\"src/interfaces/IPleaCourt.sol\":53,\"test/Letterbox.t.sol\":73,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":491,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"958490d37837dacbadbc461753b700c442e12c7c042bdeb8ef769cc9bf4d4375","verifiedTreeHash":"58b9da63dfc86cfb23a6b4c5854f1d3c2cbb8738","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":4255,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.16s\nCompiler run successful!\n","passed":true},{"durationMs":190,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 622.22µs (664.05µs CPU time)\n\nRan 5 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_invalidateNonceCancelsASignedPlea() (gas: 132644)\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35521)\n[PASS] test_postDirect() (gas: 256537)\n[PASS] test_postSignedByRelayer() (gas: 351738)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.49ms (2.59ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176125, ~: 177795)\n[PASS] test_buyExactOutputOpen() (gas: 165470)\n[PASS] test_buyIsAlwaysOpen() (gas: 163868)\n[PASS] test_callbacksRefuseNonManager() (gas: 153051)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1994664)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 385652)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256667)\n[PASS] test_initialize_fromStrangerReverts() (gas: 13705031)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 14061056)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 320116)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51923)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51486)\n[PASS] test_potFeeFundsTheCourt() (gas: 2296206)\n[PASS] test_sellAfterWindowReverts() (gas: 1985309)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2230621)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 2015452)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67718)\n[PASS] test_sellInWindow() (gas: 2312751)\n[PASS] test_sellMinOutReverts() (gas: 2176689)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1960423)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1920204)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67245)\n[PASS] test_sellWhilePendingReverts() (gas: 327465)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2418039)\n[PASS] test_sellWithoutPleaReverts() (gas: 106017)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430113)\n[PASS] test_verdictRestartsTheTimer() (gas: 1952786)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 39.52ms (60.99ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 402013, ~: 379574)\n[PASS] test_appealFlow() (gas: 3537269)\n[PASS] test_appealOfApprovedReverts() (gas: 1914784)\n[PASS] test_approvalAt17Votes() (gas: 1911404)\n[PASS] test_closeWindowAfterExpiry() (gas: 1919674)\n[PASS] test_denialAt17Votes() (gas: 2952953)\n[PASS] test_emptySeasonRollsPotOver() (gas: 163557)\n[PASS] test_judgeInputChecks() (gas: 511700)\n[PASS] test_leaderboardKeepsTop25() (gas: 46530490)\n[PASS] test_medianOfMixedScores() (gas: 1822996)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3730311)\n[PASS] test_objectionNeedsBond() (gas: 2347236)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2645688)\n[PASS] test_objectionUpheld() (gas: 4237249)\n[PASS] test_objectionWindow() (gas: 2286724)\n[PASS] test_onlyOneAppeal() (gas: 3615015)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3889360)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43417)\n[PASS] test_seasonSplitsPotProRata() (gas: 5828077)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3553656)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633600)\n[PASS] test_setSeat() (gas: 119758)\n[PASS] test_submitEmitsAndStores() (gas: 271848)\n[PASS] test_submitInvalidInputsRevert() (gas: 362067)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 251306)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 39.52ms (87.74ms CPU time)\n\nRan 25 tests for test/PleaCourtRules.t.sol:PleaCourtRulesTest\n[PASS] test_amountAboveUint128Reverts() (gas: 255499)\n[PASS] test_approvedRehearingClearsTheDebt() (gas: 4090708)\n[PASS] test_boardCapacityIsFifty() (gas: 96221188)\n[PASS] test_deniedSellerGetsOneRehearingThenACooldown() (gas: 3857430)\n[PASS] test_eligibleJudgesIsTheWholeCabalForOutsiders() (gas: 2972402)\n[PASS] test_endSeasonLatchesADueDeadManSwitchAndSweeps() (gas: 476291)\n[PASS] test_freeSellsPayNoFeeAndThePotGoesToTheOwner() (gas: 3145632)\n[PASS] test_hungCabalDeniesAndStopsTheClock() (gas: 3094810)\n[PASS] test_judgeCannotScoreOwnPlea() (gas: 377242)\n[PASS] test_judgeSellerPleaSettlesWhenAllEligibleJudgesVoted() (gas: 4584334)\n[PASS] test_noPleaBeforeTheCabalIsSet() (gas: 1909012)\n[PASS] test_noRulingAfterTheRulingPeriod() (gas: 3681430)\n[PASS] test_partialFillChargesOnlyWhatThePoolTook() (gas: 3029277)\n[PASS] test_potRollsOverWhileTheGateStands() (gas: 163210)\n[PASS] test_sellerAndObjectorCannotRuleOnTheObjection() (gas: 2643221)\n[PASS] test_sellerCannotObjectToOwnSale() (gas: 2544247)\n[PASS] test_settleSaleOnlyFromTheHook() (gas: 29843)\n[PASS] test_strikeAfterSeasonEndForfeitsThePrize() (gas: 4025286)\n[PASS] test_strikeInTheCurrentSeasonLeavesEarlierPrizesAlone() (gas: 5998687)\n[PASS] test_strikePromotesTheBench() (gas: 52177171)\n[PASS] test_upheldObjectionStrikesADeniedPleaSoNoAppealRunsDuringTheBan() (gas: 5474134)\n[PASS] test_upheldObjectionStrikesAPendingPleaAndBlocksAppeals() (gas: 4299219)\n[PASS] test_upheldObjectionStrikesTheOpenWindowAndBlocksSales() (gas: 3859921)\n[PASS] test_withdrawRefusedOnceVotesAreCast() (gas: 2073825)\n[PASS] test_withdrawVotedPleaAllowedAfterTheSwitch() (gas: 393737)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 56.30ms (122.68ms CPU time)\n\nRan 5 test suites in 57.49ms (142.46ms CPU time): 92 tests passed, 0 failed, 0 skipped (92 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.invalidateNonce()\",\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.claimPrize(uint256)\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.settleSale(uint256,uint256)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":264,\"SECURITY.md\":172,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":65,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":834,\"src/PleaHook.sol\":253,\"src/interfaces/IPleaCourt.sol\":59,\"test/Letterbox.t.sol\":87,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":514,\"test/PleaCourtRules.t.sol\":576,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3573,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/PleaCourt.sol:561: PleaCourt.endSeason() (src/PleaCourt.sol#561-599) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:778: PleaCourt.prizeUnlockAt(uint256) (src/PleaCourt.sol#778-781) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:614: PleaCourt.claim() (src/PleaCourt.sol#614-621) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:602: PleaCourt.claimPrize(uint256) (src/PleaCourt.sol#602-611) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/PleaCourt.sol:570: PleaCourt.endSeason().totalPoints (src/PleaCourt.sol#570) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:575: PleaCourt.endSeason().paid (src/PleaCourt.sol#575) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:574: PleaCourt.endSeason().winners (src/PleaCourt.sol#574) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:633: PleaCourt._award(address,uint256).minIdx (src/PleaCourt.sol#633) is a local variable never initialized\n[medium/medium] unused-return at src/PleaHook.sol:173: PleaHook.unlockCallback(bytes) (src/PleaHook.sol#173-200) ignores return value by poolManager.settle() (src/PleaHook.sol#196)\n[low/medium] missing-zero-check at src/PleaCourt.sol:224: PleaCourt.constructor(address).hook_ (src/PleaCourt.sol#224) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:80: PleaHook.constructor(IPoolManager,address,address,address).factory_ (src/PleaHook.sol#80) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:80: PleaHook.constructor(IPoolManager,address,address,address).token_ (src/PleaHook.sol#80) lacks a zero-check on :\n[low/medium] reentrancy-events at src/PleaHook.sol:112: Reentrancy in PleaHook.beforeInitialize(address,PoolKey,uint160) (src/PleaHook.sol#112-125):\n[low/medium] reentrancy-events at src/Letterbox.sol:32: Reentrancy in Letterbox.post(string,uint256) (src/Letterbox.sol#32-35):\n[low/medium] reentrancy-events at src/Letterbox.sol:38: Reentrancy in Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#38-53):\n[low/medium] timestamp at src/PleaCourt.sol:478: PleaCourt.object(uint256) (src/PleaCourt.sol#478-500) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:711: PleaCourt.deadManDeadline() (src/PleaCourt.sol#711-715) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:628: PleaCourt._award(address,uint256) (src/PleaCourt.sol#628-649) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:602: PleaCourt.claimPrize(uint256) (src/PleaCourt.sol#602-611) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:539: PleaCourt.reclaimObjectionBond(uint256) (src/PleaCourt.sol#539-547) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:778: PleaCourt.prizeUnlockAt(uint256) (src/PleaCourt.sol#778-781) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:684: PleaCourt._latchDeadMan() (src/PleaCourt.sol#684-689) uses timestamp for comparisons\n[low/medium] timestamp at src/Letterbox.sol:38: Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#38-53) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:653: PleaCourt._strike(address,uint256) (src/PleaCourt.sol#653-671) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:242: PleaCourt.authorizeSale(address,uint256) (src/PleaCourt.sol#242-261) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:718: PleaCourt.deadManTimer() (src/PleaCourt.sol#718-722) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:783: PleaCourt._sorted(uint256) (src/PleaCourt.sol#783-800) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:704: PleaCourt.tradesFreely() (src/PleaCourt.sol#704-708) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:314: PleaCourt._submit(address,string,uint256,bool) (src/PleaCourt.sol#314-343) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:410: PleaCourt.appeal(uint256) (src/PleaCourt.sol#410-428) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:506: PleaCourt.ruleObjection(uint256,bool) (src/PleaCourt.sol#506-536) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:561: PleaCourt.endSeason() (src/PleaCourt.sol#561-599) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:753: PleaCourt.saleWindow(address) (src/PleaCourt.sol#753-759) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:461: PleaCourt.closeWindow(uint256) (src/PleaCourt.sol#461-466) uses timestamp for comparisons","passed":true},{"durationMs":728,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/PleaCourt.sol:279: Centralization Risk (5 places)\n[low] costly-loop at src/PleaCourt.sol:282: Costly operations inside loop (5 places)\n[low] internal-function-used-once at src/HookFlags.sol:26: Internal Function Used Only Once\n[low] large-numeric-literal at src/PLEAToken.sol:11: Large Numeric Literal (2 places)\n[low] require-revert-in-loop at src/PleaCourt.sol:286: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/PleaCourt.sol:235: Address State Variable Set Without Checks\n[low] unchecked-return at src/PleaHook.sol:196: Unchecked Return\n[low] uninitialized-local-variable at src/PleaCourt.sol:574: Uninitialized Local Variable\n[low] unused-error at src/PleaHook.sol:47: Unused Error","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"96971422ca25f21d757bdf8f66f1b72fc3bbced9421d4f9fe7552cec2ed7d754","verifiedTreeHash":"f0e7da62dcff7dbf78c34de4bd123dcaff4212a2","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":3147,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.05s\nCompiler run successful!\n","passed":true},{"durationMs":144,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/PLEAToken.t.sol:PLEATokenTest\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 51545)\n[PASS] test_noMintFunction() (gas: 30064)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 34284)\n[PASS] test_transferMovesExactly() (gas: 78817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 686.25µs (652.26µs CPU time)\n\nRan 4 tests for test/Letterbox.t.sol:LetterboxTest\n[PASS] test_onlyLetterboxMaySubmitFor() (gas: 35499)\n[PASS] test_postDirect() (gas: 249467)\n[PASS] test_postSignedByRelayer() (gas: 344711)\n[PASS] test_postSignedRejectsForgeryAndExpiry() (gas: 161949)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 24.06ms (6.43ms CPU time)\n\nRan 33 tests for test/PleaHook.t.sol:PleaHookTest\n[PASS] testFuzz_buyNeverBlocked(uint256) (runs: 256, μ: 176910, ~: 177829)\n[PASS] test_buyExactOutputOpen() (gas: 165490)\n[PASS] test_buyIsAlwaysOpen() (gas: 163888)\n[PASS] test_callbacksRefuseNonManager() (gas: 153140)\n[PASS] test_deadManFreesSellsEverywhere() (gas: 1988104)\n[PASS] test_deadManIsPermanentEvenIfTheCabalWakesUp() (gas: 378623)\n[PASS] test_deployAtWrongAddressReverts() (gas: 12473)\n[PASS] test_factoryCanRemoveLiquidity() (gas: 136630)\n[PASS] test_factoryCollectsFeesWithZeroLiquidityChange() (gas: 256687)\n[PASS] test_initialize_fromStrangerReverts() (gas: 20622230)\n[PASS] test_initialize_poolWithoutTokenReverts() (gas: 20978232)\n[PASS] test_initialize_secondPoolReverts() (gas: 49788)\n[PASS] test_noTimerWithoutPendingPlea() (gas: 311248)\n[PASS] test_ownerIsTheGivenOwnerNotTheFactory() (gas: 51835)\n[PASS] test_permissionsMatchAddress() (gas: 10668)\n[PASS] test_poolBound() (gas: 51469)\n[PASS] test_potFeeFundsTheCourt() (gas: 2213317)\n[PASS] test_sellAfterWindowReverts() (gas: 1929096)\n[PASS] test_sellAtWindowEdgeSucceeds() (gas: 2147404)\n[PASS] test_sellByOtherSellerWindowReverts() (gas: 1959568)\n[PASS] test_sellExactOutputThroughRouterReverts() (gas: 67745)\n[PASS] test_sellInWindow() (gas: 2227685)\n[PASS] test_sellMinOutReverts() (gas: 2109423)\n[PASS] test_sellMoreThanApprovedReverts() (gas: 1904539)\n[PASS] test_sellThroughRouterRevertsEvenWithApprovedPlea() (gas: 1866560)\n[PASS] test_sellThroughRouterRevertsWithoutPlea() (gas: 67272)\n[PASS] test_sellWhilePendingReverts() (gas: 318498)\n[PASS] test_sellWholeAmountClosesWindow() (gas: 2328352)\n[PASS] test_sellWithoutPleaReverts() (gas: 103804)\n[PASS] test_sellZeroReverts() (gas: 39571)\n[PASS] test_setPotFee() (gas: 97415)\n[PASS] test_strangerCannotAddLiquidity() (gas: 1430069)\n[PASS] test_verdictRestartsTheTimer() (gas: 1850370)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 25.21ms (66.20ms CPU time)\n\nRan 25 tests for test/PleaCourt.t.sol:PleaCourtTest\n[PASS] testFuzz_voteSideFollowsTheBar(uint8,uint8,bool) (runs: 256, μ: 391746, ~: 369482)\n[PASS] test_appealFlow() (gas: 3391487)\n[PASS] test_appealOfApprovedReverts() (gas: 1860981)\n[PASS] test_approvalAt17Votes() (gas: 1857496)\n[PASS] test_closeWindowAfterExpiry() (gas: 1866050)\n[PASS] test_denialAt17Votes() (gas: 2821293)\n[PASS] test_emptySeasonRollsPotOver() (gas: 136253)\n[PASS] test_judgeInputChecks() (gas: 496112)\n[PASS] test_leaderboardKeepsTop25() (gas: 45063353)\n[PASS] test_medianOfMixedScores() (gas: 1769020)\n[PASS] test_objectionDismissedForfeitsBond() (gas: 3489956)\n[PASS] test_objectionNeedsBond() (gas: 2264112)\n[PASS] test_objectionUnruledBondReclaim() (gas: 2557951)\n[PASS] test_objectionUpheld() (gas: 3970672)\n[PASS] test_objectionWindow() (gas: 2201270)\n[PASS] test_onlyOneAppeal() (gas: 3395067)\n[PASS] test_pointsAccumulateAcrossPleas() (gas: 3752730)\n[PASS] test_seasonEndTooEarlyReverts() (gas: 43285)\n[PASS] test_seasonSplitsPotProRata() (gas: 5477374)\n[PASS] test_seatMultiplierLowersTheBar() (gas: 3404108)\n[PASS] test_setJudgesOnlyOwnerAndExactly30() (gas: 1633792)\n[PASS] test_setSeat() (gas: 119780)\n[PASS] test_submitEmitsAndStores() (gas: 264856)\n[PASS] test_submitInvalidInputsRevert() (gas: 344437)\n[PASS] test_unconfiguredCabalCannotJudge() (gas: 244619)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 26.99ms (60.32ms CPU time)\n\nRan 4 test suites in 27.78ms (76.94ms CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":53,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Letterbox.post(string,uint256)\",\"Letterbox.postSigned(address,string,uint256,uint256,bytes)\",\"PLEAToken.approve(address,uint256)\",\"PLEAToken.transfer(address,uint256)\",\"PLEAToken.transferFrom(address,address,uint256)\",\"PleaCourt.appeal(uint256)\",\"PleaCourt.authorizeSale(address,uint256)\",\"PleaCourt.bind(address)\",\"PleaCourt.claim()\",\"PleaCourt.closeWindow(uint256)\",\"PleaCourt.endSeason()\",\"PleaCourt.judge(uint256,uint8,uint8)\",\"PleaCourt.object(uint256)\",\"PleaCourt.reclaimObjectionBond(uint256)\",\"PleaCourt.ruleObjection(uint256,bool)\",\"PleaCourt.setJudges(address[])\",\"PleaCourt.setSeat(address,bool)\",\"PleaCourt.submitPlea(string,uint256)\",\"PleaCourt.submitPleaFor(address,string,uint256)\",\"PleaCourt.triggerDeadManSwitch()\",\"PleaCourt.withdrawPlea(uint256)\",\"PleaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PleaHook.renounceOwnership()\",\"PleaHook.sell(uint256,uint256)\",\"PleaHook.setLiquidityProvider(address,bool)\",\"PleaHook.setPotFeeBps(uint16)\",\"PleaHook.transferOwnership(address)\",\"PleaHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":214,\"SECURITY.md\":107,\"foundry.toml\":19,\"remappings.txt\":5,\"src/HookFlags.sol\":34,\"src/Letterbox.sol\":58,\"src/PLEAToken.sol\":16,\"src/PleaCourt.sol\":676,\"src/PleaHook.sol\":262,\"src/interfaces/IPleaCourt.sol\":53,\"test/Letterbox.t.sol\":73,\"test/PLEAToken.t.sol\":43,\"test/PleaBase.t.sol\":127,\"test/PleaCourt.t.sol\":491,\"test/PleaHook.t.sol\":405,\"test/floor/OwnerChecks.sol\":21,\"test/mocks/FactoryStub.sol\":72,\"test/mocks/MockERC20.sol\":15,\"test/mocks/Swapper.sol\":69},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3432,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/PleaCourt.sol:460: PleaCourt.endSeason() (src/PleaCourt.sol#460-484) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PleaCourt.sol:487: PleaCourt.claim() (src/PleaCourt.sol#487-494) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/PleaCourt.sol:466: PleaCourt.endSeason().totalPoints (src/PleaCourt.sol#466) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:470: PleaCourt.endSeason().winners (src/PleaCourt.sol#470) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PleaCourt.sol:506: PleaCourt._award(address,uint256).minIdx (src/PleaCourt.sol#506) is a local variable never initialized\n[medium/medium] unused-return at src/PleaHook.sol:172: PleaHook.unlockCallback(bytes) (src/PleaHook.sol#172-198) ignores return value by poolManager.settle() (src/PleaHook.sol#194)\n[low/medium] missing-zero-check at src/PleaCourt.sol:193: PleaCourt.constructor(address).hook_ (src/PleaCourt.sol#193) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:82: PleaHook.constructor(IPoolManager,address,address,address).token_ (src/PleaHook.sol#82) lacks a zero-check on :\n[low/medium] missing-zero-check at src/PleaHook.sol:82: PleaHook.constructor(IPoolManager,address,address,address).factory_ (src/PleaHook.sol#82) lacks a zero-check on :\n[low/medium] reentrancy-events at src/Letterbox.sol:37: Reentrancy in Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#37-52):\n[low/medium] reentrancy-events at src/PleaHook.sol:114: Reentrancy in PleaHook.beforeInitialize(address,PoolKey,uint160) (src/PleaHook.sol#114-127):\n[low/medium] reentrancy-events at src/Letterbox.sol:31: Reentrancy in Letterbox.post(string,uint256) (src/Letterbox.sol#31-34):\n[low/medium] timestamp at src/Letterbox.sol:37: Letterbox.postSigned(address,string,uint256,uint256,bytes) (src/Letterbox.sol#37-52) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:339: PleaCourt.appeal(uint256) (src/PleaCourt.sol#339-356) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:440: PleaCourt.reclaimObjectionBond(uint256) (src/PleaCourt.sol#440-448) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:630: PleaCourt.leaderboard() (src/PleaCourt.sol#630-642) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:387: PleaCourt.object(uint256) (src/PleaCourt.sol#387-408) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:210: PleaCourt.authorizeSale(address,uint256) (src/PleaCourt.sol#210-229) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:616: PleaCourt.saleWindow(address) (src/PleaCourt.sol#616-622) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:269: PleaCourt._submit(address,string,uint256,bool) (src/PleaCourt.sol#269-293) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:501: PleaCourt._award(address,uint256) (src/PleaCourt.sol#501-522) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:547: PleaCourt._latchDeadMan() (src/PleaCourt.sol#547-552) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:567: PleaCourt.tradesFreely() (src/PleaCourt.sol#567-571) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:524: PleaCourt._strike(address) (src/PleaCourt.sol#524-534) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:371: PleaCourt.closeWindow(uint256) (src/PleaCourt.sol#371-376) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:574: PleaCourt.deadManDeadline() (src/PleaCourt.sol#574-578) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:581: PleaCourt.deadManTimer() (src/PleaCourt.sol#581-585) uses timestamp for comparisons\n[low/medium] timestamp at src/PleaCourt.sol:460: PleaCourt.endSeason() (src/PleaCourt.sol#460-484) uses timestamp for comparisons","passed":true},{"durationMs":661,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/PleaCourt.sol:234: Centralization Risk (5 places)\n[low] costly-loop at src/PleaCourt.sol:237: Costly operations inside loop (5 places)\n[low] internal-function-used-once at src/HookFlags.sol:26: Internal Function Used Only Once\n[low] large-numeric-literal at src/PLEAToken.sol:11: Large Numeric Literal (2 places)\n[low] require-revert-in-loop at src/PleaCourt.sol:241: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/PleaCourt.sol:204: Address State Variable Set Without Checks\n[low] unchecked-return at src/PleaHook.sol:194: Unchecked Return\n[low] uninitialized-local-variable at src/PleaCourt.sol:470: Uninitialized Local Variable\n[low] unused-error at src/PleaCourt.sol:67: Unused Error (2 places)\n[low] unused-public-function at src/PleaCourt.sol:611: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9c7a576f9769d697670cbbb2c6f9aa47353403c2baedb6a2634b203e49ab7adf","verifiedTreeHash":"0f32c06bf7a35c0742e15e60f56c0dea2a0e93be","verifierVersion":"0.1.0+d372f99e"}]}