{"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":"fab3fa4d-cc24-497f-a047-560e97cedf6f","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"0f6fc20bf7f767619e5bc3013ec8eb1ec375ac4d91ce9f8b5d3ce470a66a1d35","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"4fcf33a10a2ff9b1ee425505b99f0d83b64f134bce509430cb022e511910ed7c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"0cf130790c4e61b4af861b42d2d44feb029ecd19a6c085a16dedf48c2e3a4057","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"9985914c1b4b74796a34236a07bf939d3aaec1bd7a2e30dc9543707df5bda5d4","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"c54ae8ac334dd3cad0322489b46641268d34a349130ba067edc26a4ecd2a5003","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"87a4100a1083847f3152019230dc13db44085b6de486905149d7f7fed62c890c","dependsOn":[],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"58b061604ae59fd97dd0c6651f04d38063acf8a28cf05b55e869ee47735c261b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"e5a438dc871cc21e0dc2a34c6a6427b1b98d1bfc111f0dc8989c83f8fa9a8e14","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Forecast Bounty Protocol · FORECAST\n\nA forecasting bounty protocol. A creator posts a numeric question with\na reward and a deadline. Anyone submits a forecast backed by a\nrefundable bond. When the creator resolves with the true value, the\nclosest forecast wins the reward. Every participant withdraws their bond\nback in full — losing a forecast costs nothing but gas.\n\nFORECAST is the reward and bond asset: bounties are denominated in\nFORECAST and resolvers stake FORECAST for the right to resolve, so a\nwrong resolution costs them real value.\n\nSUPPLY AND ALLOCATION\n\nTotal supply 1,000,000,000 FORECAST, 18 decimals.\n\nThe factory splits supply three ways in a single transaction:\n- poolBps 5000 (50%) opens the Uniswap v4 liquidity pool\n- 10% is taken automatically by the factory for the swarm\n- the remaining 40% goes to the requester wallet via remainderTo\n\nThe 10% swarm allocation is automatic and is NOT a DeploymentConfig\nparameter. No swarm address may be requested, invented, or configured.\n\nBountySubsidy and ResolverBootstrap are NOT funded by the factory split.\nThe requester funds them after launch by transferring FORECAST from\ntheir own wallet. Both contracts expose a permissionless deposit() that\naccepts FORECAST from any wallet and credits the pool. No owner, no\nprivileged funding path, no withdraw by any admin.\n\nCONTRACTS\n\n1. ForecastToken (ERC-20)\n   name \"Forecast Bounty\", symbol FORECAST, 18 decimals.\n   Supply 1,000,000,000 minted once to msg.sender in the constructor.\n   No owner, no mint, no burn, no pause, no transfer fee, no blacklist,\n   no upgrade path.\n\n2. ForecastBounty\n   createBounty(question, deadline, resolutionSource, rewardAmount):\n     the creator transfers rewardAmount FORECAST into the contract.\n     Requires deadline in the future and rewardAmount >= MIN_REWARD\n     (5,000e18). Emits BountyCreated.\n   forecast(bountyId, value): the submitter posts BOND (100e18)\n     FORECAST, fully refundable. One forecast per address per bounty;\n     resubmission reverts. Reverts when entries reach MAX_ENTRIES (500).\n   resolve(bountyId, trueValue): callable only by an address staking at\n     least RESOLVER_STAKE (25,000e18) in ResolverStake. Single pass over\n     entries, tracks the smallest absolute error and the winner. Marks\n     the bounty resolved and opens the dispute window. Emits\n     BountyResolved with the true value and the winner.\n   claim(bountyId): the winning submitter withdraws the reward.\n   withdrawBond(bountyId): every participant withdraws their bond in\n     full after resolution, winner or not. Must never be gated by any\n     admin switch.\n   refundReward(bountyId): the creator reclaims the reward when the\n     bounty resolved with zero entries, or when the deadline passed and\n     the creator never resolved.\n\n3. ResolverStake\n   Resolvers stake FORECAST to earn the right to resolve bounties.\n   Stake is withdrawable only when the resolver has no bounty inside an\n   open dispute window. A resolver whose resolution is successfully\n   disputed loses SLASH_BPS (20%) of its stake, paid to the disputer.\n   deposit() is permissionless.\n\n4. DisputeWindow\n   After resolve(), a 48-hour window lets any wallet dispute by posting\n   an equal bond. A dispute escalates to a three-signer panel; majority\n   (2 of 3) decides. The losing side's bond goes to the winning side.\n   Panel signers are immutable constructor arguments, distinct and\n   non-zero; the constructor reverts with DuplicateSigner or ZeroAddress\n   otherwise.\n\n5. BountySubsidy\n   Holds FORECAST deposited by the requester after launch. Releases\n   FORECAST to a bounty creator only when the bounty reward meets\n   MIN_REWARD and the creator has no other subsidised bounty open. Caps\n   total release at the deposited balance. deposit() is permissionless.\n\n6. BountyRegistry\n   Append-only index of every bounty: id, question hash, creator,reward, deadline, state, resolution value. No deletion, no owner\n   rewrite.\n\nDEPLOYMENT PARAMETERS\n\nAll fixed in DeploymentConfig as static constructor arguments:\n\n- MIN_REWARD            = 5,000e18\n- BOND                 = 100e18\n- RESOLVER_STAKE       = 25,000e18\n- SLASH_BPS            = 2000\n- DISPUTE_WINDOW       = 48 hours\n- MAX_ENTRIES          = 500\n- poolBps              = 5000\n- remainderTo          = 0xbb85c1b7540d9e7b56f7a595fc1ff7dd07cb0823\n- disputeSigner1       = 0xAF5a05077Da3e3D130d66C1C73362306cc89d899\n- disputeSigner2       = 0xC4F6e66C24fDc6Ae0f859c468dd177f8100b4959\n- disputeSigner3       = 0xc9b5c6b29F66D526b76e68198F70d66342b0d84c\n\nAll three dispute signers are Ethereum mainnet addresses, distinct,\nnon-zero, and are EOAs (no deployed code). They are immutable.\n\nRULES\n\nNo owner, no pause, no proxy on any contract. Static constructor\narguments only. Individual forecasts stay hidden until resolve() — no\ngetter may expose submitted values before resolution. Tests must pass:\nforge test, and forge test --fork-url on mainnet.\n\nChain: Ethereum mainnet. Pair with ETH. Source on GitHub.\n\nToken name: Forecast Bounty\nToken symbol: FORECAST","parentJobId":null,"planHash":"b76cb4b74f10e9f1a8fbcf4244bde783f853cb1efb9c4fddf8553d01a3cd7494","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"fab3fa4d-cc24-497f-a047-560e97cedf6f","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2069-forecast-bounty"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51315","feedbackHash":"918ec73c81f9fc937a8d5f9cc3eb752622a31ffbb75a7f0090826b910636a0fa","nodeKey":"audit_economics","submissionHash":"0f6fc20bf7f767619e5bc3013ec8eb1ec375ac4d91ce9f8b5d3ce470a66a1d35","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51882","feedbackHash":"6ba966e6386706e6b1910008534d1cf796a91ee2df1124ee423553ca7c73d12f","nodeKey":"audit_flow","submissionHash":"4fcf33a10a2ff9b1ee425505b99f0d83b64f134bce509430cb022e511910ed7c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51154","feedbackHash":"f63ec608d97fa2adf1cfa3c406ae16cde6d769dd0024711a89c723934c895cfd","nodeKey":"audit_judge","submissionHash":"0cf130790c4e61b4af861b42d2d44feb029ecd19a6c085a16dedf48c2e3a4057","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52222","feedbackHash":"e6d95e726577d94690814c4746b7e363bfb8875338e12b7537ea3029a01e6a4c","nodeKey":"audit_judge","submissionHash":"b6da7d7666b2b3aa3dd36b65eadf2ea54cfa70b314aa879bb29b8ab00e75144f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52017","feedbackHash":"d7a815dbef9d118a2ea01a815692871767c6ca9c4c4aeff4cd5d24bd9b2d4c31","nodeKey":"audit_judge","submissionHash":"945a9acd56ddc7779afa2604f8c3ffba70ccb71b771c1e4693a59d2fa9b8994d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52680","feedbackHash":"d8080175ab762d3c88bc9bf0de2c53489618fd1e4618fe00e8f95251b9aacefb","nodeKey":"audit_math","submissionHash":"9985914c1b4b74796a34236a07bf939d3aaec1bd7a2e30dc9543707df5bda5d4","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51558","feedbackHash":"0809cbedeb585f491c2883b240ef51ce2d2bd9d96cf044151f3baa7e19eea238","nodeKey":"audit_permissions","submissionHash":"c54ae8ac334dd3cad0322489b46641268d34a349130ba067edc26a4ecd2a5003","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51485","feedbackHash":"36e653ceac9c7de5460f920fafe5b58d3cff84aae7c7546e2e068b26d8de1def","nodeKey":"build_contract_project","submissionHash":"c2fee6ae60d8483cd6f42204863299138b1eb64b7627153e7f4e60f4cafc854b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51128","feedbackHash":"403b4f34eb83595300d562fc7db630b65eb41c4480454bde2177a87476be7fe5","nodeKey":"build_contract_project","submissionHash":"87a4100a1083847f3152019230dc13db44085b6de486905149d7f7fed62c890c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51747","feedbackHash":"bcdb2f535c4d3b68798e3323c0ecf5bfde56a5c6704f15d37e35698ba1be2b87","nodeKey":"build_contract_project","submissionHash":"04055a52cacefc8a0ffc326c71673d81c233b3f68d77b22e66a539f30950acb0","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52182","feedbackHash":"79ab7b326a6736f64d2f17ad2e66881f536aa6dcffb3a1d6f62a4440e8c2ef65","nodeKey":"manifest","submissionHash":"ca2287a63a26c0b615ccb58024615954f8a617983fe264b44439e4d6ae5a454b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51140","feedbackHash":"4994f6d5047b30d4385d06c386f0aa0ea9e3afb37700a1997e78cb01800e901b","nodeKey":"manifest","submissionHash":"58b061604ae59fd97dd0c6651f04d38063acf8a28cf05b55e869ee47735c261b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52207","feedbackHash":"e7a0c008cc09238e237d3d7f2b928d4b7336c4ec0f291dc76a256b0a2b01dbe8","nodeKey":"manifest","submissionHash":"fe0e5e77a1556deaba4d10567a599a5192a1cdcfb2558230a2e723bab89e8923","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51250","feedbackHash":"41f328a56ced31274a93d0845273157cd2ed8f499e2daa5b3936a41210528eb6","nodeKey":"write_foundry_tests","submissionHash":"6358c78cfdf0a46317bf424c7afae0cdda5fddfa8ae24fac47dbe274db7d1192","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51171","feedbackHash":"f5fdc9b0312d74f63b85d2a0e044278437ee547abc52b84a9607345793c6e1b0","nodeKey":"write_foundry_tests","submissionHash":"0f2fade3417427da3bcd740245acff3fb7201641af6b8f9c38286482098341fb","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51024","feedbackHash":"31d8e761be08e0ac015479c609dbbae100108b7a7c1aa1ebc6f9d1aea81063f2","nodeKey":"write_foundry_tests","submissionHash":"e5a438dc871cc21e0dc2a34c6a6427b1b98d1bfc111f0dc8989c83f8fa9a8e14","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"99784409877184f57cb20753e5b0785fbe2c6c91dfba7751001baeef3f0c94dd","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"461d9f4dfcb712ec9bd971379b9975c942012cb1a9b2999cb1999888d59c350d","device":"e15fc098eeb318a3","findings":[],"hash":"04055a52cacefc8a0ffc326c71673d81c233b3f68d77b22e66a539f30950acb0","nodeId":"9fcec85a-2fb0-482a-ad99-899e6f0c429d","outcome":"completed","summary":"Implemented the protocol, vendored dependencies, tests, and deployment/operations documentation.\n\nVerified:\n- `forge build`\n- `forge fmt --check`\n- 64 tests passing locally and on a mainnet fork using the supplied seed\n\nForecast privacy is getter-level only; calldata and storage remain public. Assumptions and operational responsibilities are documented in `README.md`, with results in `VALIDATION.md`.","treeHash":"915602c7c9718bfb0bae7af014c6400c417cd9a9","usage":{"cachedInputTokens":3683328,"inputTokens":144109,"model":"gpt-6-astra","outputTokens":74619,"runtime":"codex","turns":11,"wallClockMs":2141593}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"27168b1ff68f84d2","findings":[{"citation":"resolved","description":"Settlement of the prior round first. The medium finding (id ac00696a61b7, slash paid to the overturned resolver or skipped on an empty bounty) is FIXED: ForecastBounty._finalize now sets slashTo = decision.disputer and only redirects to item.winner when overturned && correctedValue != finalValue && item.winner != address(0) && item.winner != decision.resolver, and calls resolverStake.settle(bountyId, overturned, slashTo) so every Overturned outcome is slashed. The attached proof Proof_ac00696a61b7.t.sol passes on this tree; my scratch re-run of case (b) (empty bounty, Overturned) leaves stakeOf at 20,000e18 with 5,000e18 paid to the challenger plus 200e18 of bonds, and the creator can still refundReward and withdraw the remaining stake; the honest third-value case pays the slash to the corrected winner. The author's two new tests exist in test/DisputeWindow.t.sol. Full suite: 81 tests pass, forge fmt --check clean, all runtimes under 11 KB. No blocking finding remains. The one new observation is in the manifest added this round: launch.json pool.pairedCurrency is the IMD pair token, and its notes say 'paired with IMD at ... as the launch chose', whereas the brief says 'Pair with ETH', README.md line 54 says 'Pair | Native ETH; Uniswap v4 Currency encoding address(0)' and DeploymentConfig.pairedCurrency is address(0) (asserted in test/Deployment.t.sol). The manifest's pool block matches the launch's own pool facts given in the reference (same pairedCurrency, fee 12500, tickSpacing 60, initialPrice), both currencies are admissible, and no contract reads DeploymentConfig.pairedCurrency, so this is not a code or deployment defect. It is recorded so the requester confirms which pairing is intended and the README is aligned with whichever it is; the rest of launch.json is consistent with the constructors (argument counts and order match every constructor, addresses lowercase, names unique and under 32 characters, notes 2,301 characters, dependency order correct).","line":63,"path":"launch.json","reproduction":"Read launch.json line 63 (pairedCurrency 0xd34a99bc...) and its notes ('paired with IMD'), README.md line 54 ('Native ETH ... address(0)') and src/DeploymentConfig.sol line 19 (pairedCurrency = address(0)). Expected: one consistent statement of the paired currency across brief, README and manifest. Actual: the manifest says IMD, the documentation and brief say native ETH. For the settled finding: copy .imd/reads/proofs/Proof_ac00696a61b7.t.sol under test/scratch/ and run forge test --match-path 'test/scratch/*': it passes (creator wallet+stake falls by exactly 5,000e18).","severity":"info","snippet":"    \"pairedCurrency\": \"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\",","title":"Settled: third-value slash routing fix holds (prior medium ac00696a61b7 is fixed); launch.json pairs the pool with IMD while the brief, README and DeploymentConfig state native ETH"},{"citation":"resolved","description":"Unchanged from the prior round and re-run this round; the author disputes it as deliberate anti-farming after the agreed subsidy-drain fix, and the README documents it. The brief's 'only when' is a necessary condition so the stricter rule does not contradict it. Not a defect; recorded for the requester, who may ask for the middle-ground rule (free the slot when a later bounty by the same creator finalizes with entries) as a scope change.","line":48,"path":"src/BountySubsidy.sol","reproduction":"donor transfers 300e18 to BountySubsidy and calls deposit(). creator createBounty(A, deadline now+1 day); subsidize(A) -> +100e18. At the deadline creator refundReward(A) -> Refunded. creator createBounty(B); subsidize(B) -> reverts OtherSubsidisedBountyOpen. Scratch test test_refundedSubsidisedBountyLocksCreator passes on this tree.","severity":"info","snippet":"                && (bounty.getBounty(other).state != BountyRegistry.State.Finalized || bounty.entryCount(other) == 0)","title":"A subsidised bounty that is refunded or finalizes with no entries permanently bars its creator from further subsidies (retained advisory, requester decision)"},{"citation":"resolved","description":"Unchanged; the author's answer (documented lack of Sybil resistance, 30,200e18 locked for 48 hours per 100e18 grant, donors fund voluntarily) is accepted. Does not block.","line":40,"path":"src/ResolverBootstrap.sol","reproduction":"donor funds ResolverBootstrap with 300e18 and calls deposit(). A: stake.deposit(bounty, 25,000e18); createBounty(5,000e18, deadline T). B forecast(id, 7). At T A.resolve(id, 7). Warp 48h; finalize(id); bootstrap(id) -> stakeOf(bounty, A) == 25,100e18; all other capital is recoverable. Repeat with a fresh A.","severity":"info","snippet":"            item.state != BountyRegistry.State.Finalized || item.resolutionOverturned","title":"ResolverBootstrap grants can be farmed with self-created, self-forecast bounties across fresh addresses (documented, retained advisory)"},{"citation":"resolved","description":"Unchanged; follows from the brief's BOND 100e18 and MAX_ENTRIES 500. The author's answer that changing them alters agreed rules is accepted. Not a code defect; recorded for the requester's parameter decision.","line":136,"path":"src/ForecastBounty.sol","reproduction":"creator stakes and createBounty(5,000e18). 500 attacker addresses forecast(id, i*10), i in 0..499. honest forecast(id, 1234) -> MaxEntries(). creator resolve(id, 1234): winner is attacker #123; after 48h finalize, claim pays 5,000e18 and 500 withdrawBond calls return 50,000e18.","severity":"info","snippet":"        if (_entries[bountyId].length >= MAX_ENTRIES) revert MaxEntries();","title":"Refundable bonds plus the fixed 500-entry cap let one actor fill every slot and capture the reward at gas cost (parameter-level, retained advisory)"},{"citation":"resolved","description":"Unchanged; the implementation requires the creator to be the staked resolver, reconciling the brief's 'when the creator resolves' with its stake requirement. No bypass; an unstaked creator's bounty ends in a refund or permissionless expire after RESOLUTION_TIMEOUT. The author's deliberate, documented reading is accepted; left for the requester to confirm. Not blocking.","line":148,"path":"src/ForecastBounty.sol","reproduction":"alice deposits 25,000e18 into ResolverStake for ForecastBounty; creator (no stake) creates bounty id with deadline T. At T alice.resolve(id, 1) -> NotCreator; creator.resolve(id, 1) -> InsufficientStake from lock.","severity":"info","snippet":"        if (msg.sender != item.creator) revert NotCreator();","title":"resolve() is creator-only, narrower than the brief's 'callable only by an address staking at least RESOLVER_STAKE' (documented reading, retained for requester confirmation)"}],"hash":"0cf130790c4e61b4af861b42d2d44feb029ecd19a6c085a16dedf48c2e3a4057","nodeId":"882559f8-bb14-4190-9547-bed6fbd60e5f","outcome":"completed","summary":"The review is complete and the findings file is written. Nothing in the tree was changed beyond the required output file. The scratch tests I ran were removed.\n\n**Outcome: the prior medium finding is fixed, and no blocking finding remains.**\n\n- **Slash routing fix holds.** In `src/ForecastBounty.sol` the finalize path now sends the slash to the disputer by default and only redirects it to the corrected winner when that winner is non-zero and not the resolver, and every Overturned outcome is slashed. The attached proof passes on this tree. My re-run of the empty-bounty case shows the stake falling to 20,000e18 with the slash and both bonds going to the challenger, and the creator can still refund the reward and withdraw the rest. The author's two new tests exist, the full suite passes with 81 tests, formatting is clean, and all runtimes are far under the size limit.\n- **One new info note on the manifest.** The new `launch.json` pairs the pool with the IMD pair token, while the brief, the README and the deployment config state native ETH. The manifest's pool block matches the launch's own pool facts in the reference and both currencies are admissible, so this is a documentation alignment question for the requester, not a defect. The rest of the manifest is consistent with every constructor.\n- **Four advisories retained as info.** The permanent subsidy slot after a refunded or empty subsidised bounty, bootstrap grant farming, the 500-entry fill, and the creator-only reading of resolve all still reproduce and remain documented design decisions for the requester. None block.\n- **Coverage.** All 34 entry points are answered, 30 as holds and 4 pointing to the info advisories.","treeHash":null,"usage":{"cachedInputTokens":566185,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":16656,"runtime":"claude","turns":21,"wallClockMs":246424}},{"artifacts":[],"attempt":1,"bundleHash":"a7a59b7f63b6143d33e444661b9731d6eb977fa28cbc1a73e9fb0cc6479025a6","device":"194b47a372650d59","findings":[],"hash":"0f2fade3417427da3bcd740245acff3fb7201641af6b8f9c38286482098341fb","nodeId":"6f8bc2b2-733e-4c01-9ca7-bbc8e400609c","outcome":"completed","summary":"Added 11 tests under `test/`, covering failure paths, five-entrant lifecycle fuzzing, component isolation, and independent stake/donation invariants.\n\n`forge build` and the full suite with the required seed pass: **75 tests, 0 failures, 0 skips**.\n\nMainnet fork execution was not performed.","treeHash":"54d631c590bf249df6fa2a1ad53f07fed6a7d642","usage":{"cachedInputTokens":2773632,"inputTokens":127180,"model":"gpt-6.1-sol","outputTokens":23442,"runtime":"codex","turns":8,"wallClockMs":422666}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e3a598aae0640402","findings":[{"citation":"resolved","description":"When the three-signer panel does not record two matching votes within PANEL_TIMEOUT (7 days), DisputeWindow.finalize returns Outcome.TimedOut and ForecastBounty._finalize maps that to State.Refunded with winner = address(0). The provisional winner's claim() then reverts NotResolved and refundReward() hands the whole reward back to the creator. In DisputeWindow._decide the TimedOut branch credits both bonds back, so the disputer loses nothing. The panel signers are three unpaid EOAs with no stake and no fee in this protocol, so an idle or split panel is an ordinary state, not an edge case. Economics: any wallet can post BOND (100e18), wait 7 days, and cancel a reward of at least MIN_REWARD (5,000e18) owed to an honest winner; the bond comes back, so the net cost is gas. The creator is the beneficiary, and a creator who regrets an honest resolution can run the same play from an alternate wallet (dispute() only rejects msg.sender == resolver). This also makes the brief's statement that a wrong resolution costs the resolver real value false in the timeout branch: resolverStake.settle is called with slash = false. The README documents the timeout refund as intended, but the brief never asked for it and it inverts the burden: a failed challenge should not undo the resolution it failed to overturn. Suggested fix: on TimedOut keep the original resolution (finalize with the original winner, return both bonds, no slash), or at minimum forfeit the disputer's bond to the winner so the attack has a price. Either keeps every other agreed rule intact.","line":272,"path":"src/ForecastBounty.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\n\n/// @notice An honest resolution plus one unanswered 100-FORECAST dispute converts the winner's\n/// reward into a creator refund. The disputer's bond is returned, so cancelling a payout costs gas only.\ncontract PanelTimeoutCancelsWinnerTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n\n    address creator = makeAddr(\"creator\");\n    address winner = makeAddr(\"winner\");\n    address loser = makeAddr(\"loser\");\n    address griefer = makeAddr(\"griefer\");\n    address s1 = makeAddr(\"s1\");\n    address s2 = makeAddr(\"s2\");\n    address s3 = makeAddr(\"s3\");\n\n    uint256 constant MIN_REWARD = 5_000e18;\n    uint256 constant BOND = 100e18;\n    uint256 constant STAKE = 25_000e18;\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(address(token), BOND, 48 hours, s1, s2, s3);\n        stake = new ResolverStake(address(token), STAKE, 2_000);\n        bounty = new ForecastBounty(address(token), address(registry), address(stake), address(disputes), MIN_REWARD, BOND, 500);\n        _fund(creator);\n        _fund(winner);\n        _fund(loser);\n        _fund(griefer);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function testUnansweredDisputeStripsTheHonestWinner() public {\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"value?\", block.timestamp + 1 days, \"source\", MIN_REWARD);\n        vm.prank(winner);\n        bounty.forecast(id, 100);\n        vm.prank(loser);\n        bounty.forecast(id, 500);\n        vm.prank(creator);\n        stake.deposit(address(bounty), STAKE);\n\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 100); // honest: winner is exactly right\n        assertEq(bounty.getBounty(id).winner, winner);\n\n        // Any wallet posts 100 FORECAST with an arbitrary wrong value.\n        vm.prank(griefer);\n        disputes.dispute(address(bounty), id, 999);\n\n        // Panel never votes (it is unpaid and has no incentive). Seven days pass.\n        vm.warp(block.timestamp + 7 days);\n        bounty.finalize(id);\n        // On the current code the griefer's 100 bond is fully credited back here (TimedOut returns both bonds),\n        // so cancelling the payout cost gas only. Not asserted, so that a fix which forfeits it still passes.\n\n        // Expected: a challenge that never reached a majority leaves the original resolution standing and the winner is paid.\n        // Actual: the bounty is Refunded, claim() reverts and the creator takes the reward back.\n        vm.prank(winner);\n        bounty.claim(id);\n        assertEq(bounty.getBounty(id).winner, winner);\n        assertTrue(bounty.getBounty(id).rewardPaid);\n    }\n}","reproduction":"1) creator createBounty(reward 5,000e18, deadline T). 2) winner forecast(id, 100); loser forecast(id, 500). 3) creator stakes 25,000e18 in ResolverStake and at T calls resolve(id, 100): getBounty(id).winner == winner. 4) any wallet griefer calls DisputeWindow.dispute(bounty, id, 999) posting 100e18. 5) no signer votes; warp 7 days. 6) anyone calls bounty.finalize(id). Actual: state == Refunded, winner == address(0); winner.claim(id) reverts NotResolved; creator.refundReward(id) receives 5,000e18; griefer.withdrawPayout() returns the full 100e18. Expected: the original resolution stands (or the disputer at least pays for the failed challenge) and the winner is paid. Scratch test test/scratch/PanelTimeoutCancelsWinner.t.sol fails with NotResolved() on this code.","severity":"medium","snippet":"        if (outcome == DisputeWindow.Outcome.TimedOut) {\n            item.state = BountyRegistry.State.Refunded;","title":"An unanswered dispute turns the provisional winner's reward into a creator refund at zero cost to the disputer"},{"citation":"resolved","description":"The brief says the 48-hour window 'lets any wallet dispute'. The implementation admits exactly one dispute per case: the second caller reverts AlreadyDisputed. dispute() only excludes msg.sender == item.resolver, so a creator that is a contract (or an EOA using a helper in the same block) can resolve with a false value and immediately occupy the only slot from a second address with an arbitrary filler value. From then on no honest participant can challenge; the panel is the only remaining recourse and it must use voteValue with the exact true value, because the convenience vote(bool) offers only the resolver's two chosen numbers. Economics of the slash: _decide credits 2*BOND to item.disputer on Overturned and ForecastBounty._finalize passes decision.disputer to ResolverStake.settle, which transfers SLASH_BPS (20%, i.e. 5,000e18 of a 25,000e18 stake) to that same helper address. Measured in the scratch test: with a 40,000e18 starting balance across the bot and its helper, after a wrong resolution, a self-dispute and a diligent panel overturning it with the true value, the group holds 35,000e18 = 40,000e18 minus only the 5,000e18 reward the honest winner was always owed. The resolver's net cost for lying is zero, the honest winner never had a chance to dispute, and if the panel is lazy (vote(bool)) or idle (finding 1) the lie pays. The brief's guarantee 'a wrong resolution costs them real value' is void whenever the resolver controls two addresses. The README acknowledges a resolver 'can use a separate wallet to challenge itself and receive its own slash' but the exclusion of every other challenger is the part that makes it exploitable. Suggested fix within the brief: accept multiple disputes with distinct corrected values during the window (each posting BOND), let the panel's matched value pick the winning side, pay losing bonds to the winning side, and pay the slash to the forecaster whose entry becomes the winner or to the disputer whose value the panel matched rather than to whichever wallet was first.","line":105,"path":"src/DisputeWindow.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\n\n/// @dev Second address under the creator's control; only needed because dispute() rejects msg.sender == resolver.\ncontract Helper {\n    function dispute(LaunchToken token, DisputeWindow w, address market, uint256 id, int256 v) external {\n        token.approve(address(w), type(uint256).max);\n        w.dispute(market, id, v);\n    }\n\n    function withdraw(DisputeWindow w) external {\n        w.withdrawPayout();\n    }\n}\n\n/// @dev A creator that is a contract. It resolves and, in the same transaction, fills the only dispute slot.\ncontract CreatorBot {\n    LaunchToken immutable token;\n    ForecastBounty immutable bounty;\n    DisputeWindow immutable disputes;\n    Helper public immutable helper;\n\n    constructor(LaunchToken t, ForecastBounty b, DisputeWindow d) {\n        token = t;\n        bounty = b;\n        disputes = d;\n        helper = new Helper();\n        t.approve(address(b), type(uint256).max);\n        t.approve(address(b.resolverStake()), type(uint256).max);\n    }\n\n    function create() external returns (uint256) {\n        return bounty.createBounty(\"value?\", block.timestamp + 1 days, \"source\", 5_000e18);\n    }\n\n    function stakeUp() external {\n        bounty.resolverStake().deposit(address(bounty), 25_000e18);\n    }\n\n    function resolveAndSelfDispute(uint256 id, int256 wrong, int256 filler) external {\n        bounty.resolve(id, wrong);\n        token.transfer(address(helper), 100e18);\n        helper.dispute(token, disputes, address(bounty), id, filler);\n    }\n}\n\ncontract SingleDisputeSlotTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    CreatorBot bot;\n\n    address honest = makeAddr(\"honest\");\n    address shill = makeAddr(\"shill\");\n    address s1 = makeAddr(\"s1\");\n    address s2 = makeAddr(\"s2\");\n    address s3 = makeAddr(\"s3\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(address(token), 100e18, 48 hours, s1, s2, s3);\n        stake = new ResolverStake(address(token), 25_000e18, 2_000);\n        bounty = new ForecastBounty(address(token), address(registry), address(stake), address(disputes), 5_000e18, 100e18, 500);\n        _fund(honest);\n        _fund(shill);\n        bot = new CreatorBot(token, bounty, disputes);\n        token.transfer(address(bot), 40_000e18);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _setupWrongResolution() internal returns (uint256 id) {\n        id = bot.create();\n        bot.stakeUp();\n        vm.prank(honest);\n        bounty.forecast(id, 100); // honest forecaster: exactly right\n        vm.prank(shill);\n        bounty.forecast(id, 1_000); // creator's shill\n        vm.warp(block.timestamp + 1 days);\n        // Atomic: wrong resolution (shill wins) + filler dispute from the creator's helper.\n        bot.resolveAndSelfDispute(id, 1_000, 2_000);\n        assertEq(bounty.getBounty(id).winner, shill);\n    }\n\n    function testResolverOccupiesTheOnlyDisputeSlot() public {\n        uint256 id = _setupWrongResolution();\n        // Brief: \"a 48-hour window lets any wallet dispute\". Expected: the honest wallet can dispute with the truth.\n        // Actual: AlreadyDisputed; the resolver has pre-empted the single slot.\n        vm.prank(honest);\n        disputes.dispute(address(bounty), id, 100);\n    }\n\n}","reproduction":"1) CreatorBot (a contract) createBounty(5,000e18), stakes 25,000e18. 2) honest forecast(id, 100); shill forecast(id, 1_000). 3) at the deadline CreatorBot.resolveAndSelfDispute(id, 1_000, 2_000): resolve(id, 1_000) makes shill the winner, then in the same transaction its Helper calls DisputeWindow.dispute(bounty, id, 2_000). 4) honest calls dispute(bounty, id, 100). Actual: revert AlreadyDisputed(). Expected per brief: any wallet may dispute within 48 hours. 5) If signer1 and signer2 then voteValue(bounty, id, 100): finalize() recomputes honest as winner but ResolverStake.settle sends the 5,000e18 slash and DisputeWindow credits 200e18 to the Helper, i.e. back to the resolver. Scratch test test/scratch/SingleDisputeSlot.t.sol fails with AlreadyDisputed() on this code.","severity":"medium","snippet":"        if (item.disputer != address(0)) revert AlreadyDisputed();","title":"Single dispute slot lets the resolver pre-empt every honest challenger and route its own slash back to itself"},{"citation":"resolved","description":"subsidize() pays SUBSIDY_AMOUNT (100e18) to the creator's wallet, not into the bounty's reward escrow, and the only rate limit is 'no other subsidised bounty open', evaluated with ForecastBounty.isClosed(), which returns true for State.Refunded. createBounty accepts deadline == block.timestamp + 1 and refundReward() is allowed the moment the deadline passes, so a creator can create, subsidize, and refund every two blocks, recycling the same 5,000e18 reward. Each cycle extracts 100e18 of donated FORECAST and nothing is ever at risk: the reward comes back in full, no forecast is required, and no resolver stake is needed. Cost per cycle is roughly 430k gas (create ~250k, subsidize ~100k, refund ~80k); at 1 gwei that is about 0.00043 ETH per 100 FORECAST, so the loop is profitable at any FORECAST price above ~4.3e-6 ETH and the pool is emptied in totalDeposited/100e18 cycles regardless of price. The donor is the victim: the pool was funded to subsidise real bounties and ends up in one wallet. The README calls out 'sequential create/refund' as a known limitation, but the brief's rule ('no other subsidised bounty open') was meant to be a cap, and treating a refunded bounty as closing the slot defeats it. Suggested fix that keeps the brief's two conditions: free the creator's slot only when the subsidised bounty reaches State.Finalized with at least one entry (not on Refunded), or pay the subsidy into the bounty's escrowed reward so it can only be collected by a winning forecaster.","line":44,"path":"src/BountySubsidy.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {BountySubsidy} from \"src/BountySubsidy.sol\";\n\n/// @notice One wallet drains the whole donated subsidy pool with a create -> subsidize -> refund loop.\n/// Each cycle takes two blocks, recycles the same 5,000 reward, never pays a forecaster, and nets 100 FORECAST.\ncontract SubsidyDrainTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    BountySubsidy subsidy;\n\n    address drainer = makeAddr(\"drainer\");\n    address donor = makeAddr(\"donor\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(address(token), 100e18, 48 hours, makeAddr(\"s1\"), makeAddr(\"s2\"), makeAddr(\"s3\"));\n        stake = new ResolverStake(address(token), 25_000e18, 2_000);\n        bounty = new ForecastBounty(address(token), address(registry), address(stake), address(disputes), 5_000e18, 100e18, 500);\n        subsidy = new BountySubsidy(address(token), address(bounty), 5_000e18);\n        token.transfer(donor, 1_000e18);\n        token.transfer(drainer, 5_000e18);\n        vm.prank(donor);\n        token.approve(address(subsidy), type(uint256).max);\n        vm.prank(donor);\n        subsidy.deposit(1_000e18);\n        vm.prank(drainer);\n        token.approve(address(bounty), type(uint256).max);\n    }\n\n    function testCreateSubsidizeRefundLoopDrainsThePool() public {\n        uint256 start = token.balanceOf(drainer);\n        for (uint256 i; i < 10; ++i) {\n            vm.startPrank(drainer);\n            uint256 id = bounty.createBounty(\"q\", block.timestamp + 1, \"s\", 5_000e18);\n            try subsidy.subsidize(id) {} catch {}\n            vm.warp(block.timestamp + 1);\n            bounty.refundReward(id); // Open -> Refunded, which isClosed() treats as freeing the slot\n            vm.stopPrank();\n        }\n        // Actual on this code: the drainer is up 1,000 FORECAST, the pool is empty, no forecaster was ever paid.\n        // Expected: a creator that never lets a subsidised bounty settle can extract at most one grant.\n        assertLe(token.balanceOf(drainer) - start, 100e18);\n    }\n}","reproduction":"State: a donor has deposited 1,000e18 into BountySubsidy. Loop 10 times from one wallet holding 5,000e18: createBounty('q', block.timestamp + 1, 's', 5,000e18); subsidize(id) -> +100e18 to the wallet; warp 1 second; refundReward(id) -> state Refunded, reward returned. Actual: after 10 cycles the wallet is up 1,000e18, subsidy.available() == 0, no forecaster was ever paid. Expected: a creator that never lets a subsidised bounty settle holds at most one grant. Scratch test test/scratch/SubsidyDrain.t.sol fails on this code (1,000e18 extracted versus 100e18 expected).","severity":"medium","snippet":"        if (other != 0 && !bounty.isClosed(other)) revert OtherSubsidisedBountyOpen();","title":"BountySubsidy is drainable by one wallet with a create -> subsidize -> refund loop that never pays a forecaster"},{"citation":"resolved","description":"Because every bond is returned in full and the entry cap is a global count rather than a per-question or per-stake limit, the cheapest griefing vector in the protocol is to fill all 500 slots. An attacker funds 500 addresses with 100e18 each (50,000e18, all refundable) and submits a grid of values; afterwards every honest forecast() reverts MaxEntries, and whatever the true value is, one of the attacker's addresses is the closest entry. If the creator resolves honestly the attacker collects the reward (>= 5,000e18) and withdraws all 50,000e18 of bonds; the cost is about 500 transactions of gas (the scratch test measures roughly 170M gas including funding). The creator's only defence is the documented refund discretion, which also denies every honest participant. This follows directly from the brief's parameters (refundable BOND, MAX_ENTRIES 500), so it is reported as a parameter-level economic weakness rather than a code defect: raising the bond, making a small part of it non-refundable for losers, or setting a per-address or per-creator slot share would each change agreed rules and need the requester's decision.","line":136,"path":"src/ForecastBounty.sol","reproduction":"1) creator createBounty(5,000e18), stakes 25,000e18. 2) 500 attacker addresses each forecast(id, i*10) for i in 0..499. 3) honest forecast(id, 1_234) -> revert MaxEntries(). 4) creator resolve(id, 1_234): winner is attacker address #123. 5) after 48h finalize, claim pays 5,000e18, and all 500 withdrawBond calls return 50,000e18. Scratch test test/scratch/EntryFill.t.sol demonstrates the sequence end to end (it passes because the behaviour is what the code intends; it is not attached as a fail-to-pass proof).","severity":"low","snippet":"        if (_entries[bountyId].length >= MAX_ENTRIES) revert MaxEntries();","title":"Refundable bonds plus the hard 500-entry cap let one actor lock out all forecasters and capture the reward at gas cost"},{"citation":"resolved","description":"Eligibility is one Finalized, non-overturned bounty with at least one entry and 25,000e18 still staked, once per creator address. Nothing requires the entry to come from a third party or the question to be real. A farmer rotates 30,200e18 of capital (25,000e18 stake + 5,000e18 reward + 100e18 resolver bond + 100e18 forecast bond) through a fresh address: create, forecast from an alt, resolve at the deadline, wait 48 hours, finalize, bootstrap(). Everything is withdrawable afterwards: the reward via the alt's claim, the bonds, and the stake including the 100e18 grant. Each 48-hour cycle yields 100e18 of donated FORECAST per address, and cycles run in parallel across addresses, so the pool drains at roughly 60% annualised on the rotated capital until empty. Donors lose; nobody else is harmed, and the capital and time requirement make it far weaker than the subsidy loop, hence low. The README documents the lack of Sybil resistance. A design-preserving mitigation is to require the winning entry to belong to an address other than the creator and to require the bounty to have had a minimum number of entries, but it still only raises the cost.","line":44,"path":"src/ResolverBootstrap.sol","reproduction":"1) address A: deposit(bounty, 25,000e18); createBounty(5,000e18, deadline T). 2) alt B forecast(id, 7). 3) at T A resolve(id, 7). 4) warp 48h, finalize(id); bootstrap(id) -> stakeOf(bounty, A) == 25,100e18. 5) B claim(id) -> 5,000e18 back; B withdrawBond; A withdraw(bounty, 25,100e18). Net: +100e18 from the pool for A/B, all capital recovered. Repeat with a new A.","severity":"low","snippet":"        if (bootstrapped[item.creator]) revert AlreadyBootstrapped();","title":"ResolverBootstrap grants can be farmed with self-created, self-forecast bounties across fresh addresses"}],"hash":"0f6fc20bf7f767619e5bc3013ec8eb1ec375ac4d91ce9f8b5d3ce470a66a1d35","nodeId":"bbf5d88d-8a96-4a70-99b2-8a87cb9b0f56","outcome":"completed","summary":"The review is complete. The findings file is at the repository root and `test/scratch/` is the only other thing I created, which the task reserves for proofs and `.gitignore` already excludes. No source, test, or configuration file was changed.\n\n**Outcome.** Function-level correctness is solid: every custody path conserves balances against its liability counter, the slash reservation invariant holds, and the state machine is consistent across ForecastBounty, DisputeWindow, ResolverStake and the registry. The defects are economic and sit in the dispute and subsidy flows. I recorded five findings and 37 coverage rows, with fail-to-pass Foundry proofs attached to the three medium ones. Each proof was run and fails on the current code for the stated reason.\n\n**Findings, most severe first**\n\n1. **Medium, `src/ForecastBounty.sol:272`.** A dispute the panel never answers is mapped to Refunded. The provisional winner's claim reverts and the creator takes the reward back, while the disputer's bond is returned in full. Any wallet can cancel a payout of at least 5,000 FORECAST for gas only. The panel signers are unpaid EOAs, so an idle panel is a normal state.\n2. **Medium, `src/DisputeWindow.sol:105`.** Only one dispute is admitted per case. A creator with two addresses resolves falsely and fills the slot in the same transaction. Honest challengers get AlreadyDisputed, and even a diligent panel sends the 5,000 slash and both bonds to the resolver's own helper. Net cost of lying measured at zero.\n3. **Medium, `src/BountySubsidy.sol:44`.** A refunded bounty frees the creator's slot, and the grant goes to the creator's wallet. A create, subsidize, refund loop every two blocks drains the donated pool with the same recycled 5,000 reward. Ten cycles extracted the whole 1,000 test pool.\n4. **Low, `src/ForecastBounty.sol:136`.** Fully refundable bonds and a global 500 cap let one actor fill every slot, lock out all forecasters and win the reward whatever the truth is. This follows from the brief's parameters, so it is flagged as a parameter decision for the requester.\n5. **Low, `src/ResolverBootstrap.sol:44`.** The bootstrap grant is farmable with self-created, self-forecast bounties and rotated capital, about 100 FORECAST per address per 48 hours.\n\n**Coverage.** All 34 listed entry points have a row. Seven are marked `finding` and the rest `holds` after tracing checks, state changes and failure paths. I added three invariant rows that hold. Nothing was left `unreached`. Static-analysis leads were checked: the divide-before-multiply in `lock` is exact quotient-remainder arithmetic, the strict equality in `DonationPool.deposit()` is correct, and the reentrancy notices are moot because the token has no hooks and every entry point is guarded.\n\n**What the judge should weigh.** Findings 1 to 3 are documented in the README as known limitations, but each has a concrete extraction path reachable by an unprivileged wallet, and the suggested fixes stay inside the brief's rules. Findings 4 and 5 are inherent to the requested parameters and are reported for the requester's decision rather than as code bugs.","treeHash":null,"usage":{"cachedInputTokens":1094700,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":49697,"runtime":"claude","turns":39,"wallClockMs":711754}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5c1e4764d4e20ef5","findings":[{"citation":"resolved","description":"Only one dispute is admitted per case and the only identity check is msg.sender != resolver. A creator-resolver that is a contract (or an EOA using a bundle) can call ForecastBounty.resolve and then DisputeWindow.dispute from a second address it controls in the same transaction. From that point every honest dispute reverts with AlreadyDisputed, so the panel can only correct the value on its own initiative (it is never presented with the true value by a challenger). Whatever the panel decides, the resolver group loses nothing: on Upheld the resolver is credited both bonds (its own 100 plus the alt's 100); on Overturned the alt is credited both bonds plus the 20% slash from ResolverStake.settle; on TimedOut both bonds return and the reward is refunded to the creator. The brief's guarantee that 'a wrong resolution costs them real value' therefore does not hold, and the challenge path the brief relies on for correction is closed to everyone but the resolver. SECURITY.md acknowledges that a resolver can challenge itself, but not that doing so atomically blocks all other challengers for the whole window. Suggested minimal fix: let later disputers replace or join a pending dispute (e.g. record the first dispute per distinct corrected value, or allow a second challenger to post a bond while the panel is still open), or route the slash to the panel/treasury-less burn instead of the disputer so self-challenge is not profit-neutral.","line":105,"path":"src/DisputeWindow.sol","reproduction":"State: creator contract R staked 25,000 at ResolverStake.deposit(bounty, 25_000e18); bounty id=1 with reward 5,000 and one entry alice=10; deadline passed. Tx from R: bounty.resolve(1, 999) then R's helper Alt (different msg.sender) calls disputes.dispute(bounty, 1, 1). Expected: an honest participant can dispute with the true value 10 and the wrong resolver loses 5,000 stake + 100 bond. Actual: honest.dispute(bounty,1,10) reverts AlreadyDisputed; after signer1/signer2 voteValue(10) and bounty.finalize(1) the slash (5,000) and both bonds (200) are credited to Alt, and R + Alt + R's stake total exactly 35,000e18 before and after (see test/scratch/Leads.t.sol testSelfDisputeNeutralisesSlashAndBlocksHonestDispute, which logs 35000e18 both before and after).","severity":"medium","snippet":"        if (item.disputer != address(0)) revert AlreadyDisputed();\n        if (msg.sender == item.resolver || correctedValue == item.originalValue) revert InvalidDispute();","title":"Resolver can capture the single dispute slot in the same transaction as resolve, locking out honest challengers and recycling its own slash"},{"citation":"resolved","description":"After a correct resolution any non-resolver may dispute with an arbitrary distinct value. If the three immutable EOAs do not produce two matching votes within 7 days, DisputeWindow.finalize decides TimedOut, ForecastBounty._finalize moves the bounty to Refunded with winner = address(0), the creator reclaims the reward and the disputer is credited its full bond. The honest winner receives nothing and the disputer paid only gas. Because the disputer's downside is zero on timeout and 100 FORECAST on an active panel, every losing participant has a cheap option to deny the winner; the frequency depends entirely on panel liveness, which the protocol cannot enforce. README documents the timeout refund as intended, so this is reported as a broken guarantee under a specific condition (panel silence) rather than a code error. A minimal mitigation that preserves the design: on TimedOut, keep the original resolution (Uncontested semantics) rather than cancelling the reward, or forfeit the disputer's bond to the winner when no signer voted for the disputer's value.","line":185,"path":"src/DisputeWindow.sol","reproduction":"creator creates id=1 (reward 5,000, deadline T+1d), stakes 25,000; alice forecasts 10, loser forecasts 500; at T+1d creator resolves 10 (alice is the correct winner). loser calls disputes.dispute(bounty,1,11) paying 100. No signer votes. At +7d anyone calls bounty.finalize(1). Expected: alice can claim 5,000. Actual: alice.claim(1) reverts NotResolved (state Refunded), creator.refundReward(1) returns the 5,000 reward, loser.withdrawPayout() + withdrawBond(1) return both 100 bonds so loser's balance is unchanged (test/scratch/Leads.t.sol testPanelSilenceLetsLoserVoidWinnerAtNoCost).","severity":"low","snippet":"        } else if (outcome == Outcome.TimedOut) {\n            credit[item.resolver] += BOND;\n            credit[item.disputer] += BOND;","title":"Panel timeout refunds the disputer's bond in full, so a losing forecaster can void a correct resolution at zero cost whenever the panel fails to reach quorum"},{"citation":"resolved","description":"subsidize only requires an Open bounty with reward >= MIN_REWARD and no other open subsidised bounty. The creator may set deadline = block.timestamp + 1, collect 100 FORECAST, and one second later call refundReward (which _expire()s the bounty to Refunded, so isClosed is true), recovering the full 5,000 reward. Repeating this empties the donor-funded pool with no capital at risk and no forecasts ever answered. README states that sequential create/refund subsidised bounties are possible and that there is no Sybil resistance, so this follows the brief's eligibility rule; it is reported because the donor pool is fully extractable by one wallet with no economic friction. A minimal fix preserving the design: only release the grant when the bounty reaches Finalized with at least one entry (or escrow the grant until then), or require the creator's subsidised bounty to have reached its deadline with entries before the slot frees.","line":44,"path":"src/BountySubsidy.sol","reproduction":"subsidy funded with 300e18 via direct transfer + deposit(). Creator c with 10,000 FORECAST: loop 3x { id = createBounty('q', now+1, 's', 5_000e18); subsidy.subsidize(id); warp +1; refundReward(id) }. Expected: at most one grant per genuinely answered bounty. Actual: c's balance ends 300e18 higher than it started and subsidy.available() == 0 (test/scratch/Leads.t.sol testSubsidyCycleDrain).","severity":"low","snippet":"        if (other != 0 && !bounty.isClosed(other)) revert OtherSubsidisedBountyOpen();","title":"BountySubsidy can be drained at gas cost by create -> subsidize -> refund cycles because a refunded bounty frees the slot and the grant is never clawed back"}],"hash":"4fcf33a10a2ff9b1ee425505b99f0d83b64f134bce509430cb022e511910ed7c","nodeId":"78986dc1-0f70-40ab-a495-c0405413655c","outcome":"completed","summary":"Review complete. The findings file is written, all three snippets match the tree, and all 34 verifier entry points plus three invariants have coverage rows.\n\n**Outcome.** The control flow of the Forecast Bounty protocol is sound on every path I traced: state transitions, timing boundaries, bond and reward accounting, lock and slash reservation, and cross-contract callbacks all behave as the README describes, and the 64-test suite passes. The defects I could substantiate are economic rather than mechanical, and each has a concrete reproduction in test/scratch/Leads.t.sol.\n\n**Findings recorded**\n\n- **Medium.** A resolver can call resolve and then dispute from a second address it controls in one transaction. The single dispute slot then rejects every honest challenger for the full window, and whichever way the panel rules, the resolver group recovers both bonds and the slash. The brief's promise that a wrong resolution costs the resolver real value does not hold. The scratch test shows the resolver's combined holdings at exactly 35,000 FORECAST before and after a deliberately wrong resolution.\n- **Low.** When the panel never reaches two matching votes, a losing forecaster who disputed with any value gets their bond back in full while the correct winner's reward is refunded to the creator. Denial of a correct resolution costs the disputer nothing under panel silence.\n- **Low.** The subsidy pool drains at gas cost by cycling create, subsidize, refund with a one-second deadline. Three cycles emptied a 300 FORECAST pool in the scratch test.\n\nAll three are acknowledged in part by the README or SECURITY.md as design limits. I reported them because each breaks a guarantee the brief states, and the judge can weigh the acknowledgments.\n\n**Coverage.** Every entry point was traced. Thirty-one hold, three carry findings. Slither and Aderyn leads were all checked: the divide-before-multiply in ResolverStake.lock is exact arithmetic, the strict equality in DonationPool is a harmless revert guard, and the reentrancy flags involve only trusted contracts and a plain ERC20. No launch.json exists in the tree yet, which is expected at this stage.","treeHash":null,"usage":{"cachedInputTokens":835516,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":38001,"runtime":"claude","turns":27,"wallClockMs":561633}},{"artifacts":[],"attempt":1,"bundleHash":"faea7d2eeb39e1a24132314e3dc69df29f6571567830e57378ce47f666bcdfeb","device":"ddc5542bddcad225","findings":[],"hash":"58b061604ae59fd97dd0c6651f04d38063acf8a28cf05b55e869ee47735c261b","nodeId":"10b94160-9837-49eb-b7ec-d3e143e5d0d3","outcome":"completed","summary":"The manifest from the previous round is already correct for the accepted tree, so no change to launch.json was needed. The brief listed no findings to address, and my verification found nothing to fix.\n\nWhat I checked:\n\n- **Structure.** The file has exactly the schema keys (kind, token, contracts, pool, notes), string ABI words, lowercase addresses, unique names of at most 32 characters, backward-only contract references, and notes under 4,000 characters.\n- **Constructors.** Every entry matches the accepted source in declaration order: BountyRegistry has no constructor, DisputeWindow takes token, bond, window and three signers, ResolverStake takes token, stake and slash basis points, ForecastBounty takes token, three contract references and three limits, and the two donation pools take token plus their component references. Values match DeploymentConfig and the brief's signer addresses.\n- **Pool.** Paired currency is the IMD token address, fee 12500, tick spacing 60, and the one-to-one initial price as the launch states them.\n- **Ownership.** No contract has an owner, so no $owner argument appears, and no swarm or privileged wallet is named.\n- **Build and tests.** `forge build` succeeds and `forge test` with the verifier's fuzz seed passes all 69 tests.\n\nThe working tree is clean, with no files changed or added outside launch.json.","treeHash":"54411cbc2cf636cefddd95f8bd592d720254c8f7","usage":{"cachedInputTokens":378309,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":4512,"runtime":"claude","turns":10,"wallClockMs":85057}},{"artifacts":[],"attempt":1,"bundleHash":"734b6f14c3e2e5e438f4a1cac083f475df2c15baf3876954b541c702bab3c42f","device":"0cf632e317dfab7a","findings":[{"description":"When the panel selects a value different from the disputer's corrected value, _finalize routes the 20% slash to the corrected winner instead of the disputer. Only the creator may resolve, and the creator may also forecast on its own bounty. If the creator's own entry is closest to the panel value, settle() transfers the slash back to the resolver: stake falls by 5,000 FORECAST and its wallet rises by 5,000, so a successfully disputed resolution costs the resolver nothing beyond its 100 bond. The brief says the slash is paid to the disputer; an honest disputer whose value was only approximately right loses the slash to the resolver it corrected.","line":285,"path":"src/ForecastBounty.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// Spec: \"A resolver whose resolution is successfully disputed loses SLASH_BPS (20%) of its stake, paid to the disputer.\"\ncontract SlashRoutingTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address alice = makeAddr(\"alice\");\n    address challenger = makeAddr(\"challenger\");\n    uint256 constant SLASH = (C.RESOLVER_STAKE * C.SLASH_BPS) / 10_000;\n\n    function setUp() public {\n        token = new LaunchToken();\n        BountyRegistry registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        address[3] memory users = [creator, alice, challenger];\n        for (uint256 i; i < 3; ++i) {\n            token.transfer(users[i], 100_000e18);\n            vm.startPrank(users[i]);\n            token.approve(address(bounty), type(uint256).max);\n            token.approve(address(stake), type(uint256).max);\n            token.approve(address(disputes), type(uint256).max);\n            vm.stopPrank();\n        }\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n    }\n\n    function _wealth(address who) internal view returns (uint256) {\n        return token.balanceOf(who) + stake.stakeOf(address(bounty), who);\n    }\n\n    /// The resolver forecasts on its own bounty, resolves with a wrong value, and the panel selects a third value\n    /// that the resolver's own forecast is closest to. The slash is routed to the corrected winner: the resolver.\n    function testOverturnedResolverMustLoseSlashEvenWhenItIsTheCorrectedWinner() public {\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"s\", C.MIN_REWARD);\n        vm.prank(creator);\n        bounty.forecast(id, 50);\n        vm.prank(alice);\n        bounty.forecast(id, 0);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 0); // wrong: alice provisionally wins\n        vm.prank(challenger);\n        disputes.dispute(address(bounty), id, 100); // corrected value 100\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 49); // panel: true value 49\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 49);\n        uint256 before = _wealth(creator);\n        bounty.finalize(id);\n        assertEq(uint8(disputes.getCase(address(bounty), id).outcome), uint8(DisputeWindow.Outcome.Overturned));\n        assertEq(bounty.getBounty(id).winner, creator);\n        // Expected: the successfully disputed resolver loses 20% of its stake. Actual: the slash comes back to it.\n        assertEq(_wealth(creator), before - SLASH, \"overturned resolver kept its slash\");\n    }\n\n    /// An overturned resolution of a bounty with no entries has no corrected winner, so no slash happens at all.\n    function testOverturnedResolutionOfEmptyBountyMustStillSlash() public {\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"s\", C.MIN_REWARD);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 5);\n        vm.prank(challenger);\n        disputes.dispute(address(bounty), id, 7);\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 9);\n        vm.prank(C.disputeSigner3);\n        disputes.voteValue(address(bounty), id, 9);\n        bounty.finalize(id);\n        assertEq(uint8(disputes.getCase(address(bounty), id).outcome), uint8(DisputeWindow.Outcome.Overturned));\n        // Expected: stake reduced by the slash. Actual: the full stake is intact.\n        assertEq(stake.stakeOf(address(bounty), creator), C.RESOLVER_STAKE - SLASH, \"overturned resolver unslashed\");\n    }\n}","reproduction":"creator stakes 25,000; creates bounty; creator forecasts 50, alice forecasts 0; creator resolves 0; challenger disputes with 100; signer1 and signer2 voteValue 49; finalize. Outcome Overturned, winner = creator. Expected creator balance+stake to drop by 5,000e18 (slash paid to disputer). Actual: unchanged (94,800e18 before and after) because slashTo = item.winner = creator.","severity":"medium","title":"Overturned resolver keeps its slash when it is the corrected winner"},{"description":"With zero entries, _closest returns address(0). When the panel picks a value other than the disputer's corrected value, slashTo becomes item.winner = address(0), and settle is called with slash=false. The resolver is released with its full stake although the case outcome is Overturned and the disputer is paid both bonds as the winning side. The brief requires a resolver whose resolution is successfully disputed to lose SLASH_BPS of its stake, paid to the disputer.","line":286,"path":"src/ForecastBounty.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// Spec: \"A resolver whose resolution is successfully disputed loses SLASH_BPS (20%) of its stake, paid to the disputer.\"\ncontract SlashRoutingTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address alice = makeAddr(\"alice\");\n    address challenger = makeAddr(\"challenger\");\n    uint256 constant SLASH = (C.RESOLVER_STAKE * C.SLASH_BPS) / 10_000;\n\n    function setUp() public {\n        token = new LaunchToken();\n        BountyRegistry registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        address[3] memory users = [creator, alice, challenger];\n        for (uint256 i; i < 3; ++i) {\n            token.transfer(users[i], 100_000e18);\n            vm.startPrank(users[i]);\n            token.approve(address(bounty), type(uint256).max);\n            token.approve(address(stake), type(uint256).max);\n            token.approve(address(disputes), type(uint256).max);\n            vm.stopPrank();\n        }\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n    }\n\n    function _wealth(address who) internal view returns (uint256) {\n        return token.balanceOf(who) + stake.stakeOf(address(bounty), who);\n    }\n\n    /// The resolver forecasts on its own bounty, resolves with a wrong value, and the panel selects a third value\n    /// that the resolver's own forecast is closest to. The slash is routed to the corrected winner: the resolver.\n    function testOverturnedResolverMustLoseSlashEvenWhenItIsTheCorrectedWinner() public {\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"s\", C.MIN_REWARD);\n        vm.prank(creator);\n        bounty.forecast(id, 50);\n        vm.prank(alice);\n        bounty.forecast(id, 0);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 0); // wrong: alice provisionally wins\n        vm.prank(challenger);\n        disputes.dispute(address(bounty), id, 100); // corrected value 100\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 49); // panel: true value 49\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 49);\n        uint256 before = _wealth(creator);\n        bounty.finalize(id);\n        assertEq(uint8(disputes.getCase(address(bounty), id).outcome), uint8(DisputeWindow.Outcome.Overturned));\n        assertEq(bounty.getBounty(id).winner, creator);\n        // Expected: the successfully disputed resolver loses 20% of its stake. Actual: the slash comes back to it.\n        assertEq(_wealth(creator), before - SLASH, \"overturned resolver kept its slash\");\n    }\n\n    /// An overturned resolution of a bounty with no entries has no corrected winner, so no slash happens at all.\n    function testOverturnedResolutionOfEmptyBountyMustStillSlash() public {\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"s\", C.MIN_REWARD);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 5);\n        vm.prank(challenger);\n        disputes.dispute(address(bounty), id, 7);\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 9);\n        vm.prank(C.disputeSigner3);\n        disputes.voteValue(address(bounty), id, 9);\n        bounty.finalize(id);\n        assertEq(uint8(disputes.getCase(address(bounty), id).outcome), uint8(DisputeWindow.Outcome.Overturned));\n        // Expected: stake reduced by the slash. Actual: the full stake is intact.\n        assertEq(stake.stakeOf(address(bounty), creator), C.RESOLVER_STAKE - SLASH, \"overturned resolver unslashed\");\n    }\n}","reproduction":"creator stakes 25,000; creates bounty with no forecasts; resolves 5; challenger disputes 7; signer1 and signer3 voteValue 9; finalize. Outcome Overturned. Expected stakeOf(creator) = 20,000e18. Actual 25,000e18.","severity":"medium","title":"Overturned resolution of an empty bounty is never slashed"},{"description":"subsidize() frees openBounty[creator] only when that bounty is Finalized with at least one entry. A subsidised bounty that is refunded (no entries, or the creator never resolved) or finalized empty keeps the slot forever, because openBounty is only overwritten by a successful subsidize, which this check blocks. The brief's rule is that the creator has no other subsidised bounty open; a Refunded bounty is closed. This is a deliberate anti-farming choice documented in the README, but it deviates from the brief's eligibility rule and permanently bars that address. Covered in test/DonationLedgerInvariant.t.sol, which models the held slot as implemented.","line":48,"path":"src/BountySubsidy.sol","reproduction":"deposit 2*SUBSIDY_AMOUNT; creator creates bounty A and subsidizes it; after deadline creator refundReward(A) (state Refunded); creator creates bounty B and calls subsidize(B). Brief: succeeds (no open subsidised bounty). Actual: reverts OtherSubsidisedBountyOpen, and keeps reverting for every later bounty.","severity":"low","title":"A refunded subsidised bounty locks the creator out of subsidies permanently"}],"hash":"6358c78cfdf0a46317bf424c7afae0cdda5fddfa8ae24fac47dbe274db7d1192","nodeId":"6f8bc2b2-733e-4c01-9ca7-bbc8e400609c","outcome":"completed","summary":"I've updated two of my tests to match the builder's latest source changes, and reported three defects in that new code instead of writing tests that treat them as correct. The full suite passes with the verifier's seed: `forge build` succeeds and `forge test` runs 78 tests, all passing.\n\n**Why two tests had broken.** Commit `e273b5a` changed three things in `src/`:\n- A panel that never reaches a majority now leaves the original resolution and winner standing. It used to refund the creator.\n- The 20% slash now goes to the corrected winner instead of the disputer when the panel picks a third value.\n- A creator's subsidy slot is freed only once their subsidised bounty is Finalized with at least one entry.\n\nThat broke the five-entrant fuzz test in `LifecycleProperties.t.sol` and the invariant in `DonationLedgerInvariant.t.sol`.\n\n**What I changed:**\n- **`test/LifecycleProperties.t.sol`:** the timeout case now checks that the bounty is Finalized, the original closest forecast wins and claims the reward, and the creator's `refundReward` reverts with `NotRefundable`. The final wallet balances still have to match exactly what each party is owed. It runs 1,000 fuzz cases and passes.\n- **`test/DonationLedgerInvariant.t.sol`:** the handler now tracks whether each creator's subsidy slot is held. Replacing a bounty either runs it to the end with an entry, which frees the slot, or refunds it, which keeps it held. A held slot must make `subsidize` revert with `OtherSubsidisedBountyOpen`. The invariant runs 256 sequences of 64 calls each, with no reverts, and passes.\n- **`test/ADDED_COVERAGE.md`:** updated to describe this and to list the reported defects.\n\n**Defects reported in `.imd-findings.json`.** The two medium ones each come with a test that fails on the current code; I ran both and they fail as described.\n1. **Medium: an overturned resolver can keep its slash (`src/ForecastBounty.sol:285`).** Only the creator can resolve, but the creator can also forecast on their own bounty. Example: the creator forecasts 50 and resolves 0, the challenger disputes with 100, and the panel picks 49. The creator's own entry is now the corrected winner, so the 5,000 FORECAST slash is paid straight back to them. Their wallet plus stake is 94,800 before and after, where it should drop to 89,800. The brief says the slash goes to the disputer.\n2. **Medium: an overturned resolution of a bounty with no forecasts is never slashed (`src/ForecastBounty.sol:286`).** With no entries there is no winner, so `settle` is called with no slash. The case is still marked Overturned and the disputer collects both bonds. The resolver keeps all 25,000 staked instead of dropping to 20,000.\n3. **Low: one refunded subsidised bounty bars the creator from subsidies for good (`src/BountySubsidy.sol:48`).** The brief only requires that the creator have no other subsidised bounty *open*, and a refunded bounty is closed. The README describes this as a deliberate guard against grant farming, so it's a departure from the brief rather than a bug in the code.\n\nNo mainnet fork run was done here, and Slither and Aderyn were not run.","treeHash":"abf3c722de21e7faff46d5b13eef34749351a653","usage":{"cachedInputTokens":921973,"inputTokens":28,"model":"claude-opus-5-5","outputTokens":16148,"runtime":"claude","turns":15,"wallClockMs":246149}},{"artifacts":[],"attempt":1,"bundleHash":"6455b2a2c37b4812a469f2460c67810a2c2b3f77ea915ade9ec6e78d8d5c1a7b","device":"48cf2e19b6c54386","findings":[],"hash":"87a4100a1083847f3152019230dc13db44085b6de486905149d7f7fed62c890c","nodeId":"9fcec85a-2fb0-482a-ad99-899e6f0c429d","outcome":"completed","summary":"The revision is complete. The one blocking finding reproduced and is fixed; the four advisory findings are answered without code changes.\n\n**What reproduced.** The reviewer's proof failed on the starting tree exactly as reported. In `ForecastBounty._finalize`, a third-value overturn sent the slash to the recomputed closest entry with no check that this entry was not the resolver's own, and when the bounty had no entries the zero winner made the call skip the slash entirely.\n\n**What I changed.**\n- `src/ForecastBounty.sol`: the slash goes to the corrected winner only when that winner is non-zero and is not the case's resolver. Otherwise it falls back to the disputer, whose challenge still succeeded. The stake settlement now slashes on every overturned outcome, so a wrong resolution always costs the resolver.\n- `test/DisputeWindow.t.sol`: two regression tests cover the creator-forecasts-on-own-bounty case and the no-entries case. Both assert the resolver's wallet plus stake falls by the slash, the disputer receives it along with both bonds, and the pools stay solvent.\n- README and SECURITY.md now describe the fallback routing.\n- `.imd-responses.json` answers all five findings. The medium is marked fixed. The four info items are disputed with the same reasoning the reviewer already accepted: they follow from the brief's fixed parameters or documented, deliberate readings, and changing them would alter agreed rules.\n\n**Verification.** `forge build`, `forge fmt --check`, and `forge test` with the verifier's fuzz seed all pass. The suite runs 70 tests with zero failures, and the reviewer's proof passes on the fixed tree.\n\nUntracked dotfiles in the working tree, including an unreadable `.gitmodules`, are environment files owned by the machine, not part of this work. The vendored forge-std library remains tracked as ordinary files.","treeHash":"84221cc4ad15ff12a8b52fba286950c62a6ef644","usage":{"cachedInputTokens":739605,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":9236,"runtime":"claude","turns":22,"wallClockMs":217893}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9df7d5d52e83c572","findings":[{"citation":"resolved","description":"Introduced by the fix for the sock-dispute finding (id ed99a0d15887). When the panel overturns with a value that differs from the disputer's correctedValue, _finalize routes the SLASH_BPS slash to item.winner, the entry closest to the panel's value. Two cases make that target wrong. (a) The creator may forecast on its own bounty (forecast() has no creator check) and only the creator can resolve. If the creator's own entry is closest to the panel's value, slashTo == the resolver: ResolverStake.settle moves 20% of the stake (5,000e18 at the minimum) out of stakeOf[creator] and transfers it to the creator's wallet, so the resolver's net position is unchanged by a resolution the panel found wrong, and the honest disputer who triggered the correction (and is credited both bonds) receives none of it. Reproduced by write_foundry_tests (id 81bd617f778e) and by me. (b) With zero entries _closest returns address(0), so slashTo == address(0) and settle is called with slash == false: the resolver is released in full although the case outcome is Overturned and the disputer is paid both bonds as the winning side. Reproduced by write_foundry_tests (id 13749bc70790) and by me. Both violate the brief's 'a resolver whose resolution is successfully disputed loses SLASH_BPS of its stake' and the 'wrong resolution costs them real value' deterrent, and in (a) funds are paid to the party being penalised. Impact is bounded: the creator funds its own reward, so there is little motive to lie, and (b) harms nobody but voids the deterrent; medium because in (a) the slash is paid to the wrong party in an ordinary honest-disputer scenario (disputer approximately right, panel exact). Merged from write_foundry_tests findings 81bd617f778e (medium) and 13749bc70790 (medium): one root cause, slashTo = item.winner without excluding the resolver or the empty case. Minimal fix preserving the agreed design: when the panel adopts a third value and item.winner is the resolver or address(0), pay the slash to the disputer (whose challenge still succeeded) or burn it to a pool address such as BountySubsidy; never call settle with slash == false on an Overturned outcome.","line":285,"path":"src/ForecastBounty.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// A resolver that forecast on its own bounty resolves to a wrong value. A challenger disputes with a\n/// value the panel does not adopt; the panel agrees on a third value for which the resolver's own entry\n/// is closest. The outcome is Overturned, yet the 20% slash is transferred to the resolver itself.\ncontract ProofResolverSelfSlash is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address alice = makeAddr(\"alice\");\n    address challenger = makeAddr(\"challenger\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        _fund(creator);\n        _fund(alice);\n        _fund(challenger);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function testOverturnedResolverLosesItsSlash() public {\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"s\", C.MIN_REWARD);\n        vm.prank(creator);\n        bounty.forecast(id, 50);\n        vm.prank(alice);\n        bounty.forecast(id, 0);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 0); // wrong resolution: alice is the provisional winner\n        vm.prank(challenger);\n        disputes.dispute(address(bounty), id, 100);\n\n        // Resolver's position after resolving: wallet + stake. The resolution bond is already spent.\n        uint256 resolverBefore = token.balanceOf(creator) + stake.stakeOf(address(bounty), creator);\n\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 49);\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 49);\n        bounty.finalize(id);\n\n        assertEq(\n            uint8(disputes.getCase(address(bounty), id).outcome),\n            uint8(DisputeWindow.Outcome.Overturned),\n            \"panel overturned the resolution\"\n        );\n        uint256 slash = C.RESOLVER_STAKE * C.SLASH_BPS / 10_000;\n        uint256 resolverAfter = token.balanceOf(creator) + stake.stakeOf(address(bounty), creator);\n        // Expected: a successfully disputed resolver loses the slash. Actual on this tree: the slash is\n        // transferred to the resolver's own wallet because its entry is the corrected winner.\n        assertEq(resolverBefore - resolverAfter, slash, \"overturned resolver must lose SLASH_BPS of its stake\");\n    }\n}","reproduction":"Case (a): creator stake.deposit(bounty, 25,000e18); createBounty(reward 5,000e18, deadline T); creator forecast(id, 50); alice forecast(id, 0); at T creator resolve(id, 0) (alice provisional winner). challenger dispute(bounty, id, 100). disputeSigner1 voteValue(bounty, id, 49); disputeSigner2 voteValue(bounty, id, 49) -> Outcome.Overturned, finalValue 49 != correctedValue 100. bounty.finalize(id): winner == creator, slashTo == creator. Expected: creator's wallet + stake falls by 5,000e18. Actual: stakeOf(bounty, creator) == 20,000e18 and creator wallet rose by 5,000e18, net change 0; credit[challenger] == 200e18 only. Proof test/scratch/Proof_ResolverSelfSlash.t.sol fails on this tree with 'overturned resolver must lose SLASH_BPS of its stake: 0 != 5000000000000000000000'. Case (b): creator staked; createBounty with no forecasts; at T resolve(id, 5); challenger dispute(bounty, id, 7); disputeSigner1 and disputeSigner3 voteValue(bounty, id, 9) -> Overturned. finalize(id). Expected stakeOf(bounty, creator) == 20,000e18. Actual 25,000e18 (scratch test test_emptyBountyOverturnedNotSlashed passes on this tree, demonstrating the behaviour).","severity":"medium","snippet":"        if (overturned && decision.correctedValue != decision.finalValue) slashTo = item.winner;\n        resolverStake.settle(bountyId, overturned && slashTo != address(0), slashTo);","title":"Third-value slash routing pays the slash to the overturned resolver itself when its own entry is the corrected winner, and skips the slash entirely when the bounty has no entries"},{"citation":"resolved","description":"Consequence of the agreed fix for the subsidy drain (id c991df634a77), which I proposed in that form. openBounty[creator] is overwritten only by a successful subsidize, and subsidize reverts while the recorded bounty is not Finalized-with-entries. A subsidised bounty that honestly received no forecasts and was refunded at its deadline, or that was resolved empty, therefore holds the slot forever: every later subsidize call from that address reverts OtherSubsidisedBountyOpen, including after the creator runs a real, answered, finalized bounty. The brief's condition 'the creator has no other subsidised bounty open' is a necessary condition ('only when'), so a stricter rule does not contradict it, and the README documents the held slot as deliberate anti-farming. Recorded for the requester's decision, not as a defect: the alternative (freeing the slot on Refunded) reopens the one-block drain. A middle ground that keeps the drain closed is to free the slot when the subsidised bounty is Finalized with entries OR when a later bounty by the same creator is Finalized with entries. From write_foundry_tests (id 84532dd54671, low); recalibrated to info.","line":48,"path":"src/BountySubsidy.sol","reproduction":"donor transfers 300e18 to BountySubsidy and calls deposit(). creator createBounty(A, deadline T); creator subsidize(A) -> +100e18. At T creator refundReward(A) -> state Refunded, isClosed(A) == true. creator createBounty(B); subsidize(B) -> reverts OtherSubsidisedBountyOpen. alice forecast(B, 1); creator stakes and resolves B at its deadline; after 48h finalize(B) (Finalized, 1 entry). creator createBounty(C); subsidize(C) -> still reverts OtherSubsidisedBountyOpen because openBounty[creator] == A. Expected by the brief's literal wording: B and C are eligible since A is closed. Actual: permanent lockout. Scratch test test_refundedSubsidisedBountyLocksCreator passes on this tree, demonstrating the sequence.","severity":"info","snippet":"                && (bounty.getBounty(other).state != BountyRegistry.State.Finalized || bounty.entryCount(other) == 0)","title":"A subsidised bounty that is refunded or finalizes with no entries permanently bars its creator from further subsidies"},{"citation":"resolved","description":"Prior advisory finding a2cebee1f0d6, unchanged this round. The author answered that it reproduces but requires 30,200e18 locked per address for 48 hours per 100e18 grant and is the documented lack of Sybil resistance. I re-ran the sequence: it still reproduces. The author's answer is accepted: the mitigation only raises cost and changes agreed eligibility, and donors fund the pool voluntarily knowing the README's statement. Kept as info so the record is complete; it does not block.","line":40,"path":"src/ResolverBootstrap.sol","reproduction":"donor transfers 300e18 to ResolverBootstrap and calls deposit(). A: stake.deposit(bounty, 25,000e18); createBounty(5,000e18, deadline T). alt B forecast(id, 7). At T A.resolve(id, 7). Warp 48h; finalize(id); bootstrap(id) -> stakeOf(bounty, A) == 25,100e18. A.withdraw(bounty, 25,100e18); A.withdrawPayout(); B.claim(id); B.withdrawBond(id). Actual: A+B recover all capital plus 100e18; available() == 200e18. Repeat with a fresh A.","severity":"info","snippet":"            item.state != BountyRegistry.State.Finalized || item.resolutionOverturned\n                || bounty.entryCount(bountyId) == 0","title":"ResolverBootstrap grants can be farmed with self-created, self-forecast bounties across fresh addresses (documented, disputed by author, retained as advisory)"},{"citation":"resolved","description":"Prior advisory finding 325b63d3d1aeb, unchanged this round. Follows directly from the brief's refundable BOND 100e18 and MAX_ENTRIES 500; the author's answer that changing it alters agreed rules is accepted. Recorded for the requester's parameter decision; not a code defect and not blocking.","line":136,"path":"src/ForecastBounty.sol","reproduction":"creator stakes 25,000e18 and createBounty(5,000e18). 500 attacker addresses each forecast(id, i*10) for i in 0..499. honest forecast(id, 1234) -> revert MaxEntries(). creator resolve(id, 1234): winner is attacker address #123. After 48h finalize; claim pays 5,000e18; 500 withdrawBond calls return 50,000e18.","severity":"info","snippet":"        if (_entries[bountyId].length >= MAX_ENTRIES) revert MaxEntries();","title":"Refundable bonds plus the fixed 500-entry cap let one actor fill every slot and capture the reward at gas cost (parameter-level, disputed by author, retained as advisory)"},{"citation":"resolved","description":"Prior advisory finding 79814c17edea, unchanged this round. The brief says both 'When the creator resolves' and 'callable only by an address staking at least RESOLVER_STAKE'; the implementation requires the creator to be the staked resolver. No bypass exists; a bounty whose creator never stakes ends in a creator refund or a permissionless expire at deadline + 7 days. The author's answer that this is deliberate and documented is accepted; left for the requester to confirm the reading. Not blocking.","line":148,"path":"src/ForecastBounty.sol","reproduction":"alice deposits 25,000e18 into ResolverStake for ForecastBounty; creator (no stake) creates bounty id with deadline T. At T alice.resolve(id, 1) -> reverts NotCreator. creator.resolve(id, 1) -> reverts InsufficientStake from lock. Expected under the ForecastBounty wording: a staked address may resolve.","severity":"info","snippet":"        if (msg.sender != item.creator) revert NotCreator();","title":"resolve() is creator-only, narrower than the brief's 'callable only by an address staking at least RESOLVER_STAKE' (documented reading, disputed by author, retained for requester confirmation)"}],"hash":"945a9acd56ddc7779afa2604f8c3ffba70ccb71b771c1e4693a59d2fa9b8994d","nodeId":"882559f8-bb14-4190-9547-bed6fbd60e5f","outcome":"completed","summary":"Review complete. The findings file is written and every snippet checks against the tree.\n\n**Settled from last round**\n\n- **Subsidy drain (medium, fixed).** Both prior proofs pass now. The creator's slot frees only after a subsidised bounty is Finalized with at least one entry, so the one-block create/refund loop stops at one grant.\n- **Sock-dispute slash recovery (medium, fixed).** The proof passes. A sock filling the slot with a bogus value no longer receives the slash when the panel picks a third value.\n- **Panel timeout voiding the winner (low, fixed).** TimedOut now finalizes with the original winner, who claims the reward, and the creator refund reverts.\n- **Bootstrap farming, 500-slot fill, creator-only resolve (disputed).** All still reproduce, but the author's answers hold: documented design choices or direct consequences of the brief's parameters. Kept as info so they do not block.\n\n**New this round, one medium at `src/ForecastBounty.sol:285`**\n\nThe sock fix introduced a wrong recipient. On a third-value overturn the slash goes to the corrected winner, and that winner can be the resolver itself, because the creator may forecast on its own bounty. The slash then moves from the creator's stake into the creator's wallet, so an overturned resolution costs it nothing and the honest disputer gets only the bonds. With zero entries the winner is the zero address and the slash is skipped entirely despite the Overturned outcome. Both come from the tester's reports and I reproduced both. The attached proof fails on the current tree with `0 != 5000e18`. It is medium rather than high because the creator funds its own reward, so the motive to lie is weak, but the brief's slash guarantee is broken and funds reach the penalised party.\n\n**Also recorded as info**\n\nThe subsidy fix permanently bars a creator whose subsidised bounty was refunded or finalized empty. That is stricter than the brief's wording but consistent with its \"only when\" condition, and loosening it reopens the drain. Left for the requester's decision.\n\n**Checks**\n\nThe full suite passes with 78 tests. Static-analysis leads were examined: the divide-before-multiply in the slash formula is exact, the strict equality in deposit() only rejects a zero top-up, and the reentrancy notices sit behind nonReentrant with trusted callees. Coverage has 37 rows answering all 34 entry points.","treeHash":null,"usage":{"cachedInputTokens":1305906,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":29392,"runtime":"claude","turns":23,"wallClockMs":467462}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d7e6877fd9e388bb","findings":[{"citation":"resolved","description":"Boundary x invariant seam. subsidize() releases SUBSIDY_AMOUNT (100e18) as soon as a bounty is Open, funded with >= MIN_REWARD and before its deadline, and the per-creator slot is freed the moment the bounty is Refunded (isClosed). ForecastBounty.refundReward lets the creator cancel an Open bounty at block.timestamp >= deadline (src/ForecastBounty.sol:214-216), and createBounty accepts deadline = block.timestamp + 1. So the whole subsidy lifecycle (create -> subsidize -> deadline -> refund -> re-create -> subsidize) fits in one block per grant, costs only gas, and recycles the same 5,000e18 of reward capital every block. The 'caps total release at the deposited balance' guarantee is the only limit, so one wallet with exactly MIN_REWARD empties the entire requester-funded pool at ~100 FORECAST per block (720,000 FORECAST/day on mainnet). README.md line 126 acknowledges sequential create/refund farming but not that it is a one-block, zero-capital-at-risk loop; donors cannot ration or recover funds, so the pool's stated purpose (subsidising real bounties) is defeated. Fix options that keep the brief's rules: release the grant only when the bounty reaches Finalized with at least one entry (or pay it into the bounty's reward escrow, where a refund returns it to the pool), or at minimum keep a creator's subsidy slot occupied until deadline + RESOLUTION_TIMEOUT regardless of refund.","line":39,"path":"src/BountySubsidy.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {BountySubsidy} from \"src/BountySubsidy.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// @notice A creator with exactly MIN_REWARD of capital drains BountySubsidy one grant per block:\n///         create (deadline = now + 1) -> subsidize -> next block refundReward -> repeat.\n///         Fails today (ten grants leave with zero cost); passes once a grant requires the bounty\n///         to actually run (or the deadline-refund fast path no longer frees the subsidy slot).\ncontract SubsidyDrainTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountySubsidy subsidy;\n    address attacker = makeAddr(\"attacker\");\n    address donor = makeAddr(\"donor\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        BountyRegistry registry = new BountyRegistry();\n        DisputeWindow disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        ResolverStake stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        subsidy = new BountySubsidy(address(token), address(bounty), C.MIN_REWARD);\n        // Requester funds the subsidy pool for ten grants.\n        token.transfer(donor, 10 * C.SUBSIDY_AMOUNT);\n        vm.startPrank(donor);\n        token.approve(address(subsidy), type(uint256).max);\n        subsidy.deposit(10 * C.SUBSIDY_AMOUNT);\n        vm.stopPrank();\n        // Attacker holds exactly one minimum reward.\n        token.transfer(attacker, C.MIN_REWARD);\n        vm.prank(attacker);\n        token.approve(address(bounty), type(uint256).max);\n    }\n\n    function testOneRewardOfCapitalDrainsTheWholeSubsidyPoolInTenBlocks() public {\n        vm.warp(1_000_000);\n        uint256 id;\n        for (uint256 i; i < 10; ++i) {\n            vm.startPrank(attacker);\n            if (id != 0) {\n                // Previous bounty hit its deadline in this block: reclaim the reward at zero cost.\n                try bounty.refundReward(id) {} catch {}\n            }\n            try bounty.createBounty(\"q\", block.timestamp + 1, \"src\", C.MIN_REWARD) returns (uint256 newId) {\n                id = newId;\n                try subsidy.subsidize(id) {} catch {}\n            } catch {}\n            vm.stopPrank();\n            vm.warp(block.timestamp + 1);\n        }\n        vm.prank(attacker);\n        try bounty.refundReward(id) {} catch {}\n\n        // Attacker still has the full reward capital back, and must not have extracted the pool.\n        assertEq(token.balanceOf(attacker) >= C.MIN_REWARD, true, \"reward capital lost\");\n        assertLe(subsidy.totalReleased(), C.SUBSIDY_AMOUNT, \"subsidy pool drained by create/refund loop\");\n    }\n}","reproduction":"State: BountySubsidy holds 1,000e18 (ten grants); attacker holds 5,000e18 FORECAST and has approved ForecastBounty. Block t: attacker.createBounty('q', t+1, 'src', 5000e18) -> id 1; attacker.subsidize(1) -> +100e18 to attacker. Block t+1: attacker.refundReward(1) (state Open, timestamp >= deadline -> _expire -> Refunded, 5,000e18 returned); attacker.createBounty('q', t+2, ...) -> id 2; subsidize(2) succeeds because isClosed(1) is true -> +100e18. Repeat eight more blocks. Expected: a creator cannot extract more than one grant without ever running a bounty. Actual: subsidy.totalReleased() == 1,000e18 (pool empty), attacker balance == 5,000e18 + 1,000e18, total cost = gas. Proof test test/scratch/SubsidyDrain.t.sol fails with 'subsidy pool drained by create/refund loop: 1000e18 > 100e18'.","severity":"high","snippet":"        if (item.reward < MIN_REWARD || item.state != BountyRegistry.State.Open || block.timestamp >= item.deadline) {","title":"BountySubsidy is drainable at zero cost: create with deadline = now+1, subsidize, refund next block, repeat"},{"citation":"resolved","description":"Only one dispute is admitted per case and the only identity check is msg.sender != resolver. A creator who resolves with a wrong value can, in the same block, call dispute() from a second wallet with any value != originalValue. Every honest challenger then reverts with AlreadyDisputed. When the panel overturns (with the correct third value via voteValue), _decide credits 2*BOND to the squatting wallet and ResolverStake.settle pays the reserved 20% slash (5,000e18 on a 25,000e18 stake) to that same wallet. The resolver family's net position after an overturned wrong resolution is exactly zero, so the brief's guarantee that 'a wrong resolution costs them real value' and 'the losing side's bond goes to the winning side' are both void; if the panel does not act within PANEL_TIMEOUT the case times out and the creator additionally gets the reward refunded. README.md line 141 acknowledges that alternate wallets can recover their own slash, but the brief's economic deterrent is the only protection against a creator picking a sock-puppet winner, and this makes it free. Possible fixes preserving the design: route the slash to a party the resolver cannot pick (e.g. the corrected winner or the subsidy pool) and/or admit multiple disputers per case so an honest challenger is never locked out.","line":105,"path":"src/DisputeWindow.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// @notice A resolver who resolves with a wrong value and, in the same block, disputes from a second\n///         wallet with a bogus value (a) locks every honest challenger out (one dispute slot) and\n///         (b) receives its own 20% slash plus both bonds when the panel overturns it.\n///         Net cost of the wrong resolution: zero. Fails today; passes once a wrong resolution\n///         costs the resolver value regardless of who filed the dispute.\ncontract SelfDisputeTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address alt = makeAddr(\"creator-second-wallet\");\n    address honest = makeAddr(\"honest-challenger\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        BountyRegistry registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        _fund(creator);\n        _fund(alt);\n        _fund(honest);\n        _fund(alice);\n        _fund(bob);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _family() internal view returns (uint256) {\n        return token.balanceOf(creator) + token.balanceOf(alt) + stake.stakeOf(address(bounty), creator)\n            + disputes.credit(creator) + disputes.credit(alt);\n    }\n\n    function testWrongResolutionCostsNothingWhenResolverSelfDisputes() public {\n        vm.warp(1_000_000);\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"src\", C.MIN_REWARD);\n        vm.prank(alice);\n        bounty.forecast(id, 10); // true answer will be 10\n        vm.prank(bob);\n        bounty.forecast(id, 20);\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n\n        uint256 familyBefore = _family(); // the reward is already escrowed and never returns to the creator\n        vm.warp(block.timestamp + 1 days);\n        // Wrong resolution: 20 makes bob the winner. Same block, second wallet squats the dispute slot.\n        vm.prank(creator);\n        bounty.resolve(id, 20);\n        vm.prank(alt);\n        disputes.dispute(address(bounty), id, 21);\n\n        // Honest challenger who knows the answer is 10 is locked out.\n        vm.prank(honest);\n        vm.expectRevert(DisputeWindow.AlreadyDisputed.selector);\n        disputes.dispute(address(bounty), id, 10);\n\n        // Panel notices and overturns with the correct value.\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 10);\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 10);\n        bounty.finalize(id);\n\n        assertEq(bounty.getBounty(id).winner, alice, \"panel corrected the winner\");\n        uint256 slash = C.RESOLVER_STAKE * C.SLASH_BPS / 10_000;\n        // The brief: a wrong resolution costs the resolver real value. Here the resolver family must be\n        // down by at least the slash. On the current code the family nets exactly zero.\n        assertLe(_family() + slash, familyBefore, \"resolver recovered its own slash and both bonds\");\n    }\n}","reproduction":"State: creator staked 25,000e18 in ResolverStake for the bounty market; alice forecast 10, bob forecast 20; true answer 10. Block t (deadline): creator.resolve(id, 20) (bob provisional winner, 100e18 bond). Same block: alt (creator's second wallet).dispute(bounty, id, 21). honest.dispute(bounty, id, 10) -> reverts AlreadyDisputed. signer1.voteValue(bounty,id,10); signer2.voteValue(bounty,id,10) -> Overturned; finalize(id). Expected: creator loses at least the 5,000e18 slash. Actual: creator balance 69,900e18 + alt balance 104,900e18 (incl. 5,000e18 slash) + stake 20,000e18 + alt credit 200e18 = 195,000e18, identical to the pre-resolution family total of 195,000e18. Proof test test/scratch/SelfDispute.t.sol fails with 'resolver recovered its own slash and both bonds: 200000e18 > 195000e18'.","severity":"medium","snippet":"        if (item.disputer != address(0)) revert AlreadyDisputed();\n        if (msg.sender == item.resolver || correctedValue == item.originalValue) revert InvalidDispute();","title":"Single dispute slot lets a resolver self-dispute from a second wallet, locking honest challengers out and recovering its own slash and both bonds"},{"citation":"resolved","description":"Boundary case of the payout branch. Outcome.Overturned is reached whenever two signers match any value != originalValue, including a value that is also != correctedValue. The disputer is paid 2*BOND plus the slash although its own claim was wrong, so the brief's 'losing side's bond goes to the winning side' is applied to a side that did not win. Combined with finding 2 this is what makes a bogus self-dispute profitable rather than merely neutral. Documented in README.md line 102 as intended; reported because the brief wording differs.","line":183,"path":"src/DisputeWindow.sol","reproduction":"resolve(id, 20) (truth 10); challenger.dispute(bounty, id, 99); signer1.voteValue(bounty,id,10); signer2.voteValue(bounty,id,10) -> Outcome.Overturned. Expected per brief: challenger's wrong claim loses; at most its own bond back. Actual: credit[challenger] == 200e18 and challenger receives 5,000e18 slash via ResolverStake.settle; resolver loses bond and 20% stake.","severity":"low","snippet":"        if (outcome == Outcome.Overturned) {\n            credit[item.disputer] += amount;","title":"Disputer whose own corrected value is wrong still receives both bonds and the 20% slash when the panel picks a third value"}],"hash":"9985914c1b4b74796a34236a07bf939d3aaec1bd7a2e30dc9543707df5bda5d4","nodeId":"528ad27e-d094-4931-a90f-e940d0035683","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` with proofs under `test/scratch/`, and no source or configuration file was changed.\n\n**What I covered**\n\nI audited the Math Precision, Boundary and Numerical Gap failure modes across all six application contracts and both tokens. The existing suite passes (64 tests). All 34 listed entry points plus the balance-conservation invariant have a coverage row. None are unreached.\n\nThe pure arithmetic holds: the XOR signed-to-unsigned distance in winner selection is correct at both int256 extremes, the quotient/remainder slash equals floor(balance × bps / 10000) exactly (the Slither divide-before-multiply lead is a false positive), the reserve-at-lock scheme keeps every pending slash funded, and every deadline, dispute-window and panel-timeout boundary uses consistent comparators with no overlap or gap. The DonationPool balance-minus-available sync cannot underflow.\n\n**Findings**\n\n- **High. BountySubsidy drains at zero cost.** The grant is released while a bounty is merely Open, and the creator's subsidy slot frees the moment the bounty is Refunded. Since a creator can refund at exactly the deadline and deadlines may be `now + 1`, one wallet holding 5,000 FORECAST loops create, subsidize, refund once per block and empties the whole pool. Proof test fails on current code with 1,000e18 released from ten cycles. Anchored at `src/BountySubsidy.sol:39`.\n- **Medium. Self-dispute squatting neutralises the slash.** Only one dispute slot exists and the only identity check is against the resolver's own address. A creator who resolves wrongly disputes from a second wallet in the same block, locking out honest challengers, and when the panel overturns it the 5,000 FORECAST slash plus both bonds flow back to that wallet. Net cost of a wrong resolution is zero, contradicting the brief. Proof test fails on current code. Anchored at `src/DisputeWindow.sol:105`.\n- **Low. A disputer with a wrong corrected value is still paid as the winning side** when the panel picks a third value. Anchored at `src/DisputeWindow.sol:183`.\n\nThe README and SECURITY.md acknowledge the second and third items and the sequential refund farming behind the first. I reported them anyway because the brief's stated guarantees are contradicted and the first item's one-block, zero-risk magnitude is not documented. The judge should weigh that acknowledgement when deciding what the author must fix.","treeHash":null,"usage":{"cachedInputTokens":1114749,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":36281,"runtime":"claude","turns":31,"wallClockMs":534590}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"357c46e3781993d4","findings":[{"citation":"resolved","description":"subsidize() pays SUBSIDY_AMOUNT (100e18) straight to the creator's wallet as soon as a bounty is Open, funded with >= MIN_REWARD and before its deadline. The only rate limit is 'no other subsidised bounty open', evaluated with ForecastBounty.isClosed(), which is true for State.Refunded. The creator controls when that clears: createBounty accepts deadline = block.timestamp + 1 (src/ForecastBounty.sol:113) and refundReward() at state Open only needs block.timestamp >= deadline (src/ForecastBounty.sol:214-216), which runs _expire() and sets Refunded. So the same wallet, recycling the same 5,000e18 reward, collects one grant per block with no capital at risk, no forecast and no resolver stake until available() == 0. The grant is never clawed back on refund. The brief's two conditions are met literally, but the limiter they describe cannot ration anything, and the README's acknowledgement of 'sequential create/refund' does not say it is a one-block, zero-cost loop. Merged from audit_math (high), audit_permissions (medium), audit_flow (low) and audit_economics (medium); all four describe this one root cause. Severity medium: donated funds are lost to a party that did nothing, but only the voluntarily funded pool is at risk and the fix needs a scope decision. Minimal fixes that keep the brief's rules: free the creator's slot only when the subsidised bounty reaches Finalized with at least one entry (not on Refunded), or pay the grant into the bounty's escrowed reward so a refund returns it to the pool, or make a subsidised bounty ineligible for the deadline-time creator refund.","line":44,"path":"src/BountySubsidy.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {BountySubsidy} from \"src/BountySubsidy.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// @notice A creator with exactly MIN_REWARD of capital drains BountySubsidy one grant per block:\n///         create (deadline = now + 1) -> subsidize -> next block refundReward -> repeat.\n///         Fails today (ten grants leave with zero cost); passes once a grant requires the bounty\n///         to actually run (or the deadline-refund fast path no longer frees the subsidy slot).\ncontract SubsidyDrainTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountySubsidy subsidy;\n    address attacker = makeAddr(\"attacker\");\n    address donor = makeAddr(\"donor\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        BountyRegistry registry = new BountyRegistry();\n        DisputeWindow disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        ResolverStake stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        subsidy = new BountySubsidy(address(token), address(bounty), C.MIN_REWARD);\n        // Requester funds the subsidy pool for ten grants.\n        token.transfer(donor, 10 * C.SUBSIDY_AMOUNT);\n        vm.startPrank(donor);\n        token.approve(address(subsidy), type(uint256).max);\n        subsidy.deposit(10 * C.SUBSIDY_AMOUNT);\n        vm.stopPrank();\n        // Attacker holds exactly one minimum reward.\n        token.transfer(attacker, C.MIN_REWARD);\n        vm.prank(attacker);\n        token.approve(address(bounty), type(uint256).max);\n    }\n\n    function testOneRewardOfCapitalDrainsTheWholeSubsidyPoolInTenBlocks() public {\n        vm.warp(1_000_000);\n        uint256 id;\n        for (uint256 i; i < 10; ++i) {\n            vm.startPrank(attacker);\n            if (id != 0) {\n                // Previous bounty hit its deadline in this block: reclaim the reward at zero cost.\n                try bounty.refundReward(id) {} catch {}\n            }\n            try bounty.createBounty(\"q\", block.timestamp + 1, \"src\", C.MIN_REWARD) returns (uint256 newId) {\n                id = newId;\n                try subsidy.subsidize(id) {} catch {}\n            } catch {}\n            vm.stopPrank();\n            vm.warp(block.timestamp + 1);\n        }\n        vm.prank(attacker);\n        try bounty.refundReward(id) {} catch {}\n\n        // Attacker still has the full reward capital back, and must not have extracted the pool.\n        assertEq(token.balanceOf(attacker) >= C.MIN_REWARD, true, \"reward capital lost\");\n        assertLe(subsidy.totalReleased(), C.SUBSIDY_AMOUNT, \"subsidy pool drained by create/refund loop\");\n    }\n}","reproduction":"State: a donor has deposited 1,000e18 into BountySubsidy (ten grants); attacker holds exactly 5,000e18 FORECAST and approved ForecastBounty. Block t: attacker.createBounty('q', t+1, 'src', 5_000e18) -> id 1; attacker.subsidize(1) -> attacker +100e18. Block t+1: attacker.refundReward(1): state Open and timestamp >= deadline -> _expire -> Refunded, 5,000e18 returned; attacker.createBounty('q', t+2, 'src', 5_000e18) -> id 2; subsidize(2) passes line 44 because isClosed(1) is true -> +100e18. Repeat eight more blocks. Expected: a creator that never lets a subsidised bounty run holds at most one grant. Actual: subsidy.available() == 0, totalReleased() == 1,000e18, attacker balance == 6,000e18, cost = gas only. Proof test/scratch/Proof_ed83a04092ff.t.sol fails on this tree with 'subsidy pool drained by create/refund loop: 1000e18 > 100e18'.","severity":"medium","snippet":"        if (other != 0 && !bounty.isClosed(other)) revert OtherSubsidisedBountyOpen();","title":"BountySubsidy: the one-open-subsidised-bounty limiter never binds; one wallet drains the whole donated pool with create -> subsidize -> refund cycles at gas cost"},{"citation":"resolved","description":"The brief says the 48-hour window 'lets any wallet dispute'. dispute() admits exactly one challenger per case (line 105, AlreadyDisputed) and the only identity check is msg.sender != item.resolver (line 106). A creator-resolver that submits a wrong value can, in the same block or the same transaction from a contract, occupy the only slot from a second address with any filler value != originalValue. Every later honest dispute reverts AlreadyDisputed, so the panel can only correct the value on its own initiative via voteValue with the exact true value; the convenience vote(bool) offers only the resolver's two numbers. _decide (lines 183-184) credits 2*BOND to item.disputer on Overturned and ForecastBounty._finalize (src/ForecastBounty.sol:286) passes decision.disputer to ResolverStake.settle, which transfers the reserved slash (20% of stake, 5,000e18 at the minimum) to that same wallet. The resolver family's net position after a wrong resolution that the panel overturns is therefore unchanged except for the reward the honest winner was always owed; on Upheld the resolver is credited both bonds; on TimedOut both bonds return and the creator gets the reward refunded (finding 3). The brief's deterrent 'a wrong resolution costs them real value' is void for anyone with two addresses, and the honest challenger who should earn the slash cannot enter. Same branch, second effect: Overturned is reached whenever two signers match any value != originalValue, including a value != correctedValue, so a disputer whose own claim was wrong is still paid both bonds and the slash (audit_math low); that is what makes a bogus self-dispute profit-neutral rather than costly. README/SECURITY acknowledge that alternate wallets can recover their own slash but not that one dispute atomically closes the challenge path to everyone else for the whole window. Merged from audit_math, audit_permissions, audit_flow and audit_economics (all medium) plus audit_math's third-value payout (low). Possible fixes that keep the design: admit several disputers per case (each posting BOND, one per distinct corrected value) and pay losing bonds and the slash to the challenger whose value the panel matched, or when the panel chose a third value, to the corrected winner; or burn the slash.","line":105,"path":"src/DisputeWindow.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// A resolver that posts a wrong value occupies the single dispute slot from a second wallet in\n/// the same block. The honest challenger is locked out with AlreadyDisputed, and when the panel\n/// corrects the value the 20% slash and both bonds are paid to the resolver's own second wallet.\ncontract SelfDisputeTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address sock = makeAddr(\"sock\");\n    address alice = makeAddr(\"alice\");\n    address challenger = makeAddr(\"challenger\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        _fund(creator);\n        _fund(sock);\n        _fund(alice);\n        _fund(challenger);\n        vm.warp(1_700_000_000);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function test_wrongResolutionCostsTheResolverItsSlash() public {\n        uint256 householdBefore = token.balanceOf(creator) + token.balanceOf(sock);\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"src\", C.MIN_REWARD);\n        vm.prank(alice);\n        bounty.forecast(id, 100); // the true value\n        vm.prank(sock);\n        bounty.forecast(id, 50);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 50); // wrong on purpose: makes the sock the provisional winner\n        vm.prank(sock);\n        disputes.dispute(address(bounty), id, 51); // bogus correction fills the only slot\n\n        // The honest challenger can no longer dispute at all.\n        vm.prank(challenger);\n        vm.expectRevert(DisputeWindow.AlreadyDisputed.selector);\n        disputes.dispute(address(bounty), id, 100);\n\n        // The panel does its job and agrees on the real value.\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 100);\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 100);\n        bounty.finalize(id);\n        assertEq(bounty.getBounty(id).winner, alice);\n\n        // Settle every position the creator household holds.\n        vm.prank(sock);\n        disputes.withdrawPayout(); // 2 * BOND\n        vm.prank(sock);\n        bounty.withdrawBond(id);\n        uint256 remaining = stake.stakeOf(address(bounty), creator);\n        vm.prank(creator);\n        stake.withdraw(address(bounty), remaining);\n        uint256 householdAfter = token.balanceOf(creator) + token.balanceOf(sock);\n\n        // Fails on the current code: the household is down only the reward it would have refunded\n        // anyway. A wrong resolution that was successfully disputed cost it no slash and no bond.\n        assertGe(\n            householdBefore - householdAfter,\n            C.MIN_REWARD + (C.RESOLVER_STAKE * C.SLASH_BPS) / 10_000,\n            \"slash recovered by the resolver's own second wallet\"\n        );\n    }\n}","reproduction":"State: creator staked 25,000e18 in ResolverStake for ForecastBounty; creator created bounty id (reward 5,000e18, deadline T); alice forecast 100 (true value); sock (creator's second wallet) forecast 50. At T: creator.resolve(id, 50) (wrong; sock is provisional winner). Same block: sock.dispute(bounty, id, 51) posting 100e18. challenger.dispute(bounty, id, 100) -> reverts AlreadyDisputed. disputeSigner1.voteValue(bounty, id, 100); disputeSigner2.voteValue(bounty, id, 100) -> Overturned; bounty.finalize(id) -> winner alice; ResolverStake.settle pays 5,000e18 slash to sock; sock.withdrawPayout() -> 200e18; sock.withdrawBond(id) -> 100e18; creator.withdraw(bounty, 20,000e18). Expected per brief: the wrong resolution costs the resolver 5,000e18 of stake plus its 100e18 resolution bond. Actual: creator+sock balances fell by exactly the 5,000e18 reward alice legitimately won and nothing else. Proof test/scratch/Proof_357b474e4bae.t.sol fails on this tree with 'slash recovered by the resolver's own second wallet: 5000e18 < 10000e18'. Third-value branch (scratch test test_wrongDisputerPaidOnThirdValue, passes, demonstrating the behaviour): resolve(id, 20) with truth 10; challenger.dispute(bounty, id, 99); two signers voteValue 10 -> credit[challenger] == 200e18 and challenger receives the 5,000e18 slash although its own value was wrong.","severity":"medium","snippet":"        if (item.disputer != address(0)) revert AlreadyDisputed();\n        if (msg.sender == item.resolver || correctedValue == item.originalValue) revert InvalidDispute();","title":"DisputeWindow: a single first-come dispute slot lets the resolver self-dispute from a second wallet, lock every honest challenger out and route both bonds plus its own 20% slash back to itself"},{"citation":"resolved","description":"When the three-signer panel records no two matching votes within PANEL_TIMEOUT (7 days), DisputeWindow.finalize decides TimedOut and ForecastBounty._finalize maps it to State.Refunded with winner = address(0). The provisional winner's claim() then reverts NotResolved and refundReward() hands the whole reward back to the creator. DisputeWindow._decide (lines 185-187) credits each side its own bond, so the disputer loses nothing, and settle is called with slash = false. A failed challenge therefore undoes the resolution it failed to overturn: the brief's 'the closest forecast wins the reward' and 'the losing side's bond goes to the winning side' both fail in this branch while the disputer paid only gas. Any losing forecaster, or the creator from a second wallet, has a free option to deny the winner whenever the panel is idle or split. README and SECURITY document the timeout refund as intended, and the panel's liveness is a stated trust assumption; with a live panel the challenge costs 100e18 and fails, so severity is low. Merged from audit_permissions (low), audit_flow (low) and audit_economics (medium). Design-preserving alternatives: on TimedOut keep the original resolution (Uncontested semantics: finalize with the original winner, return both bonds, no slash), or forfeit the disputer's bond to the winner so the attack has a price.","line":272,"path":"src/ForecastBounty.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// A correct resolution is disputed with an arbitrary value; the panel never votes.\n/// Expected (brief): the closest forecast wins the reward and the losing side's bond goes to the winner.\n/// Actual: the bounty becomes Refunded, the winner's claim reverts, the creator takes the reward back and\n/// the disputer recovers its full bond.\ncontract PanelTimeoutVoidsWinnerTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n\n    address creator = makeAddr(\"creator\");\n    address winner = makeAddr(\"winner\");\n    address loser = makeAddr(\"loser\");\n    address griefer = makeAddr(\"griefer\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        _fund(creator);\n        _fund(winner);\n        _fund(loser);\n        _fund(griefer);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function test_unansweredDisputeVoidsCorrectWinnerAtNoCostToDisputer() public {\n        uint256 deadline = block.timestamp + 1 days;\n        vm.startPrank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n        uint256 id = bounty.createBounty(\"q\", deadline, \"src\", C.MIN_REWARD);\n        vm.stopPrank();\n\n        vm.prank(winner);\n        bounty.forecast(id, 100);\n        vm.prank(loser);\n        bounty.forecast(id, 500);\n\n        vm.warp(deadline);\n        vm.prank(creator);\n        bounty.resolve(id, 100); // correct value; winner is the provisional winner\n        assertEq(bounty.getBounty(id).winner, winner);\n\n        uint256 grieferBefore = token.balanceOf(griefer);\n        vm.prank(griefer);\n        disputes.dispute(address(bounty), id, 999); // bogus challenge, 100e18 bond\n\n        // Panel never votes.\n        vm.warp(block.timestamp + C.PANEL_TIMEOUT);\n        bounty.finalize(id);\n\n        // The griefer recovers its whole bond.\n        vm.prank(griefer);\n        disputes.withdrawPayout();\n        assertEq(token.balanceOf(griefer), grieferBefore, \"disputer paid nothing for the failed challenge\");\n\n        // The correct winner should be paid; instead the bounty is Refunded and the claim reverts.\n        ForecastBounty.Bounty memory item = bounty.getBounty(id);\n        assertEq(uint8(item.state), uint8(BountyRegistry.State.Finalized), \"bounty should finalize with the original winner\");\n        assertEq(item.winner, winner, \"original winner should stand after a failed challenge\");\n        uint256 winnerBefore = token.balanceOf(winner);\n        vm.prank(winner);\n        bounty.claim(id);\n        assertEq(token.balanceOf(winner), winnerBefore + C.MIN_REWARD, \"winner is paid the reward\");\n    }\n}","reproduction":"1) creator stakes 25,000e18, createBounty(reward 5,000e18, deadline T). 2) winner forecast(id, 100); loser forecast(id, 500). 3) at T creator.resolve(id, 100): getBounty(id).winner == winner. 4) griefer.dispute(bounty, id, 999) posting 100e18. 5) no signer votes; warp 7 days. 6) anyone calls bounty.finalize(id). Actual: state == Refunded (4), winner == address(0); winner.claim(id) reverts NotResolved; creator.refundReward(id) receives 5,000e18; griefer.withdrawPayout() returns the full 100e18 so its balance is unchanged. Expected: state Finalized (3) with the original winner, who can claim 5,000e18. Proof test/scratch/PanelTimeoutVoidsWinner.t.sol fails on this tree with 'bounty should finalize with the original winner: 4 != 3'.","severity":"low","snippet":"        if (outcome == DisputeWindow.Outcome.TimedOut) {\n            item.state = BountyRegistry.State.Refunded;\n            item.winner = address(0);","title":"Panel timeout turns a correct resolution into a creator refund: any wallet can void the winner's reward with a 100e18 bond that is returned in full"},{"citation":"resolved","description":"Eligibility is one Finalized, non-overturned bounty with at least one entry and >= RESOLVER_STAKE still staked, once per creator address. Nothing requires the entry to come from an unrelated party or the question to be real. A farmer rotates 30,200e18 (25,000e18 stake + 5,000e18 reward + 100e18 resolution bond + 100e18 forecast bond) through a fresh creator address: create, forecast from an alt, resolve at the deadline, wait 48 hours, finalize, bootstrap(). Everything is withdrawable afterwards, including the 100e18 grant via ResolverStake.withdraw, so each 48-hour cycle per address extracts 100e18 of donated FORECAST; cycles run in parallel across addresses until the pool is empty. Donors lose; nobody else is harmed; the capital and time requirement make it far weaker than finding 1. README documents the lack of Sybil resistance. From audit_economics (low). A design-preserving mitigation is to require the winning entry to belong to an address other than the creator and a minimum entry count; it only raises the cost.","line":44,"path":"src/ResolverBootstrap.sol","reproduction":"State: donor transferred 300e18 to ResolverBootstrap and called deposit(). 1) A: stake.deposit(bounty, 25,000e18); createBounty(5,000e18, deadline T). 2) alt B forecast(id, 7). 3) at T A.resolve(id, 7). 4) warp 48h; finalize(id); bootstrap(id) -> stakeOf(bounty, A) == 25,100e18. 5) A.withdraw(bounty, 25,100e18); A.withdrawPayout() (resolution bond); B.claim(id) -> 5,000e18; B.withdrawBond(id). Actual: A+B hold their starting 200,000e18 plus 100e18, boot.available() == 200e18, no outside party ever participated. Expected: grants reach resolvers who answered a real question. Scratch test test/scratch/Leads.t.sol test_bootstrapFarm passes on this tree, demonstrating the sequence.","severity":"low","snippet":"        if (bootstrapped[item.creator]) revert AlreadyBootstrapped();\n        bootstrapped[item.creator] = true;","title":"ResolverBootstrap grants can be farmed with self-created, self-forecast bounties across fresh addresses; all capital is recovered plus the grant"},{"citation":"resolved","description":"Every bond is returned in full and MAX_ENTRIES is a global count, so filling all 500 slots from 500 addresses costs 50,000e18 of fully refundable capital plus gas. Afterwards every honest forecast() reverts MaxEntries and, with a grid of values, one of the attacker's addresses is the closest entry whatever the true value is; an honest creator who resolves pays the attacker the reward (>= 5,000e18) and all 500 bonds come back. The creator's only defence is the documented refund discretion, which also denies every honest participant. This follows directly from the brief's parameters (refundable BOND 100e18, MAX_ENTRIES 500), so it is recorded as a parameter-level economic weakness for the requester's decision, not a code defect: a per-address slot share, a larger bond or a partially non-refundable bond for losers would each change agreed rules. From audit_economics (low); recalibrated to info.","line":136,"path":"src/ForecastBounty.sol","reproduction":"1) creator stakes 25,000e18 and createBounty(5,000e18). 2) 500 attacker addresses each forecast(id, i*10) for i in 0..499 (each funded with 100e18). 3) honest forecast(id, 1234) -> revert MaxEntries(). 4) creator resolve(id, 1234): winner is attacker address #123. 5) after 48h finalize; claim pays 5,000e18; all 500 withdrawBond calls return 50,000e18. Scratch test test/scratch/EntryFill.t.sol test_fillAllSlots passes on this tree (about 115M gas including funding), demonstrating steps 1-4.","severity":"info","snippet":"        if (_entries[bountyId].length >= MAX_ENTRIES) revert MaxEntries();","title":"Refundable bonds plus the fixed 500-entry cap let one actor fill every slot, lock honest forecasters out and guarantee itself the reward at gas cost"},{"citation":"resolved","description":"The brief's ForecastBounty section says resolve is 'callable only by an address staking at least RESOLVER_STAKE in ResolverStake' and ResolverStake 'resolvers stake FORECAST to earn the right to resolve bounties', while its summary says 'the creator resolves'. The implementation requires both: msg.sender == item.creator (line 148) and the creator's own stake for this market >= RESOLVER_STAKE (lock at line 160). README states the creator-only reading was chosen deliberately. Consequence: a staked third-party resolver can never resolve, and a bounty whose creator never stakes 25,000e18 can only end in a creator refund or a permissionless expire at deadline + 7 days, with participants' bonds held up to seven days and no payout possible. No bypass exists; recorded so the requester confirms the intended reading. If the broader reading is wanted, dropping the creator check suffices: lock/open already key the dispute and slash path on the resolver address passed in. From audit_permissions (info).","line":148,"path":"src/ForecastBounty.sol","reproduction":"State: alice deposited 25,000e18 into ResolverStake for ForecastBounty; creator (no stake) created bounty id with deadline T. At T: alice.resolve(id, 1) -> reverts NotCreator. Expected under the brief's ForecastBounty wording: a staked address may resolve. Actual: only the creator may, and a creator with no stake cannot resolve either (lock reverts InsufficientStake). Scratch test test/scratch/Leads.t.sol test_stakedThirdPartyCannotResolve passes on this tree.","severity":"info","snippet":"        if (msg.sender != item.creator) revert NotCreator();","title":"resolve() is creator-only, narrower than the brief's 'callable only by an address staking at least RESOLVER_STAKE'; a bounty whose creator never stakes can only end in a refund"}],"hash":"b6da7d7666b2b3aa3dd36b65eadf2ea54cfa70b314aa879bb29b8ab00e75144f","nodeId":"882559f8-bb14-4190-9547-bed6fbd60e5f","outcome":"completed","summary":"The review is complete. The findings file is written and validated, every snippet matches the tree, all 34 entry points have a coverage row, and the three attached proofs fail on the current code. Git shows no tracked file changed; test/scratch/ is gitignored.\n\n**Outcome.** I kept 6 findings out of the 15 the specialists reported, after merging duplicates and reproducing each one. No critical or high defects. The code itself is sound; every kept issue is an economic or design-level weakness that the brief's own rules permit and that README/SECURITY partly acknowledge.\n\n**Findings in .imd-findings.json**\n\n1. **Medium, BountySubsidy drain.** A creator makes a bounty with a one-second deadline, takes the 100 FORECAST grant, refunds next block, and repeats. The \"one open subsidised bounty\" limiter clears on Refunded, so one wallet empties the donated pool at gas cost. Merged four specialist reports. Proof attached, fails on this tree.\n2. **Medium, single dispute slot.** A resolver who posts a wrong value disputes from a second wallet in the same block. Honest challengers revert with AlreadyDisputed, and on overturn both bonds and the 20% slash go to the resolver's own wallet. The brief's \"wrong resolution costs real value\" guarantee does not hold. The third-value payout branch is folded in. Merged five reports. Proof attached, fails.\n3. **Low, panel timeout voids the winner.** A bogus dispute that the panel never answers moves the bounty to Refunded, the correct winner cannot claim, the creator takes the reward back, and the disputer recovers its bond in full. Low because it needs the panel to be idle, a stated trust assumption. Merged three reports. My own proof attached, fails.\n4. **Low, ResolverBootstrap farming** with self-created, alt-forecast bounties across fresh addresses. Reproduced in a scratch test.\n5. **Info, 500-slot fill** with refundable bonds locks honest forecasters out. Reproduced, but it follows from the brief's parameters.\n6. **Info, resolve is creator-only**, narrower than one reading of the brief. Reproduced; no bypass exists.\n\n**Dropped or recalibrated.** Nothing was dropped outright. The subsidy drain was downgraded from the high one specialist gave it, since only the voluntarily funded pool is at risk. The timeout finding was set to low rather than medium for the trust-assumption reason above.\n\n**My own pass.** I traced the state machine, bond and reward accounting, the stake lock and slash arithmetic, dispute window boundaries, the zero-entry refund path, and the manifest against the constructors. All held. The existing 75-test suite passes and the largest runtime is well under the size limit.","treeHash":null,"usage":{"cachedInputTokens":928318,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":37590,"runtime":"claude","turns":27,"wallClockMs":509949}},{"artifacts":[],"attempt":1,"bundleHash":"70ae1a6d94459169aacfd29ac739cecadd48494e03ff642582a1d2deaf475e24","device":"234a4a00ca903cb9","findings":[],"hash":"c2fee6ae60d8483cd6f42204863299138b1eb64b7627153e7f4e60f4cafc854b","nodeId":"9fcec85a-2fb0-482a-ad99-899e6f0c429d","outcome":"completed","summary":"Both required findings reproduced and are fixed. Both proof tests now pass, and so do the full `forge test` (11 suites, run with the verifier's fuzz seed), `forge build` and `forge fmt --check`.\n\n**Required fixes**\n- **Subsidy drain (c991df63):** a subsidised bounty now frees its creator for another grant only after it reaches Finalized with at least one entry. If it is refunded, expires or finalizes with no entries, that creator can never get another grant. The fix is in `src/BountySubsidy.sol`. The trade-off is that one refund locks an honest creator out for good; the README says so.\n- **Resolver disputing itself from a second wallet (ed99a0d1):** I kept the single dispute slot, because the proof test expects a second dispute to revert with `AlreadyDisputed`. Instead, the 20% slash goes to the disputer only if the panel's value exactly matches the value the disputer claimed. If the panel picks a different value, the slash goes to the corrected winner, or nobody is slashed when the bounty has no entries. The disputer still gets both bonds when the resolution is overturned. The fix is in `ForecastBounty._finalize`, with two new tests. One gap remains, and the docs state it: a second wallet that disputes with the exact true value still collects the slash.\n\n**Advisory findings**\n- **Panel timeout (4d743caa):** fixed. If the panel never reaches a majority, the original resolution now stands with its original winner. Both bonds are returned and nobody is slashed; before, the creator got the reward back. I updated the two timeout tests to match.\n- **Bootstrap farming, 500-slot fill, creator-only `resolve` (a2cebee1, 325b63d3, 79814c17):** answered \"disputed\" with no code change. They come from rules the brief sets and are already documented. The creator-only `resolve` is a reading of the brief that the requester should confirm.\n\nMy answers to all six findings are in `.imd-responses.json`, and README and SECURITY are updated to match.\n\nSome untracked files I didn't create have appeared in the repo root, including `.gitmodules`, `.bashrc` and `.claude/`. I left them alone. You may want to look at `.gitmodules` before submitting, since the rules forbid leaving a git submodule.","treeHash":"62649b6244120508426f94d24324a8980648615b","usage":{"cachedInputTokens":567739,"inputTokens":18,"model":"claude-opus-5-5","outputTokens":8892,"runtime":"claude","turns":9,"wallClockMs":180424}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"323aec17bfc8d652","findings":[{"citation":"resolved","description":"Trust gap (access x economics). subsidize() is correctly gated to the bounty's creator, and the grant arithmetic is correct, but the only rate limit on the permitted actor is 'no other subsidised bounty still open' (BountySubsidy.sol:43-44, isClosed = Finalized || Refunded). The actor permitted by the guard controls when that condition clears: createBounty accepts deadline = block.timestamp + 1 (ForecastBounty.sol:113) and refundReward() at state Open needs only block.timestamp >= deadline (ForecastBounty.sol:214-216) and moves the bounty to Refunded. So the same wallet, reusing the same 5,000 FORECAST reward, can create, subsidize and refund in consecutive blocks and collect SUBSIDY_AMOUNT (100 FORECAST) each block until the pool is empty. The brief's text 'no other subsidised bounty open' is satisfied literally, but the pool's intent (subsidising real questions) is defeated at a cost of gas only; the subsidy is paid out immediately rather than being tied to the bounty resolving. README.md documents sequential create/refund as a known limitation, so the author may decide to accept it; it is reported because it is a complete, unprivileged, zero-cost drain of requester-donated funds. Minimal fix preserving the design: make a subsidised bounty ineligible for the deadline-time creator refund (or require a minimum deadline distance such as DISPUTE_WINDOW for eligibility), or release the grant only when the subsidised bounty finalizes with entries rather than at subsidize time; each is a scope decision for the requester.","line":44,"path":"src/BountySubsidy.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {BountySubsidy} from \"src/BountySubsidy.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// One creator wallet, one 5,000 FORECAST reward reused, zero bounty duration: the whole\n/// subsidy pool is extracted in ten consecutive blocks. The \"no other subsidised bounty open\"\n/// limiter never binds because refundReward at the (one-second) deadline closes the slot.\ncontract SubsidyDrainTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    BountySubsidy subsidy;\n    address creator = makeAddr(\"creator\");\n    address donor = makeAddr(\"donor\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        subsidy = new BountySubsidy(address(token), address(bounty), C.MIN_REWARD);\n        token.transfer(creator, C.MIN_REWARD);\n        token.transfer(donor, 1_000e18);\n        vm.prank(creator);\n        token.approve(address(bounty), type(uint256).max);\n        vm.prank(donor);\n        token.approve(address(subsidy), type(uint256).max);\n        vm.warp(1_700_000_000);\n    }\n\n    function test_singleCreatorCannotDrainSubsidyPoolWithZeroDurationBounties() public {\n        vm.prank(donor);\n        subsidy.deposit(1_000e18); // ten grants\n        uint256 before = token.balanceOf(creator);\n        for (uint256 i; i < 10; ++i) {\n            vm.startPrank(creator);\n            uint256 id = bounty.createBounty(\"q\", block.timestamp + 1, \"src\", C.MIN_REWARD);\n            try subsidy.subsidize(id) {} catch {}\n            vm.warp(block.timestamp + 1);\n            bounty.refundReward(id);\n            vm.stopPrank();\n        }\n        // Fails on the current code: the creator holds the entire 1,000 FORECAST pool and\n        // its original 5,000 reward, having never kept a bounty open for more than one second.\n        assertLt(token.balanceOf(creator) - before, 1_000e18, \"whole pool extracted by one creator\");\n    }\n}","reproduction":"State: donor deposited 1,000e18 into BountySubsidy; creator holds 5,000e18 and approved ForecastBounty. Loop ten times from the creator wallet: (1) createBounty('q', block.timestamp + 1, 'src', 5_000e18) -> id; (2) subsidize(id) -> creator receives 100e18; (3) next block (timestamp + 1): refundReward(id) -> state Refunded, 5,000e18 returned. Expected: a creator that never kept a question open or resolved anything should not be able to collect more than one grant / the limiter should bind. Actual: after ten blocks subsidy.available() == 0 and the creator's balance grew by exactly 1,000e18 with the same 5,000e18 reward reused every cycle (test test_singleCreatorCannotDrainSubsidyPoolWithZeroDurationBounties fails on this tree with 'whole pool extracted by one creator: 1000e18 >= 1000e18').","severity":"medium","snippet":"        if (other != 0 && !bounty.isClosed(other)) revert OtherSubsidisedBountyOpen();","title":"BountySubsidy: the one-open-subsidised-bounty limiter never binds, so one creator drains the whole pool with zero-duration bounties"},{"citation":"resolved","description":"Trust gap (access x economics x asymmetry). dispute() admits exactly one challenger per case (DisputeWindow.sol:105) and the only identity check is msg.sender != item.resolver (line 106). The slash reserved at lock time and both bonds are paid to item.disputer on Overturned (DisputeWindow.sol:183-184, ResolverStake.settle line 94). A creator-resolver that submits a wrong value can, in the same block, dispute from any second wallet with a throwaway correctedValue; every later honest dispute reverts AlreadyDisputed, and when the panel agrees on the real value via voteValue the 20% stake slash (5,000e18 at the minimum stake) and the 200e18 of bonds are credited to the resolver's own wallet. The brief's stated deterrent, 'resolvers stake FORECAST ... so a wrong resolution costs them real value', is therefore neutralised by an unprivileged actor at gas cost, and the honest challenger who would have earned the slash is denied the dispute path entirely. The panel still corrects the winner, so the harm is to the incentive model and to the blocked challenger, not to the reward. README.md records that 'alternate wallets can recover their own slash' as a trust limit; it is reported here because the single-slot lockout is a concrete mechanism that makes the self-dispute reliable rather than a race. Possible fixes that keep the design: admit more than one disputer (first distinct correctedValue per wallet, each with a bond) and pay the slash to the challenger whose value the panel selected or, when the panel chose a third value, to the corrected winner/forecasters instead of the first disputer; or burn the slash. Each changes the stated 'paid to the disputer' rule and needs a scope decision.","line":105,"path":"src/DisputeWindow.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {ForecastBounty} from \"src/ForecastBounty.sol\";\nimport {BountyRegistry} from \"src/BountyRegistry.sol\";\nimport {ResolverStake} from \"src/ResolverStake.sol\";\nimport {DisputeWindow} from \"src/DisputeWindow.sol\";\nimport {DeploymentConfig as C} from \"src/DeploymentConfig.sol\";\n\n/// A resolver that posts a wrong value occupies the single dispute slot from a second wallet in\n/// the same block. The honest challenger is locked out with AlreadyDisputed, and when the panel\n/// corrects the value the 20% slash and both bonds are paid to the resolver's own second wallet.\ncontract SelfDisputeTest is Test {\n    LaunchToken token;\n    ForecastBounty bounty;\n    BountyRegistry registry;\n    ResolverStake stake;\n    DisputeWindow disputes;\n    address creator = makeAddr(\"creator\");\n    address sock = makeAddr(\"sock\");\n    address alice = makeAddr(\"alice\");\n    address challenger = makeAddr(\"challenger\");\n\n    function setUp() public {\n        token = new LaunchToken();\n        registry = new BountyRegistry();\n        disputes = new DisputeWindow(\n            address(token), C.BOND, C.DISPUTE_WINDOW, C.disputeSigner1, C.disputeSigner2, C.disputeSigner3\n        );\n        stake = new ResolverStake(address(token), C.RESOLVER_STAKE, C.SLASH_BPS);\n        bounty = new ForecastBounty(\n            address(token), address(registry), address(stake), address(disputes), C.MIN_REWARD, C.BOND, C.MAX_ENTRIES\n        );\n        _fund(creator);\n        _fund(sock);\n        _fund(alice);\n        _fund(challenger);\n        vm.warp(1_700_000_000);\n    }\n\n    function _fund(address who) internal {\n        token.transfer(who, 100_000e18);\n        vm.startPrank(who);\n        token.approve(address(bounty), type(uint256).max);\n        token.approve(address(stake), type(uint256).max);\n        token.approve(address(disputes), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function test_wrongResolutionCostsTheResolverItsSlash() public {\n        uint256 householdBefore = token.balanceOf(creator) + token.balanceOf(sock);\n        vm.prank(creator);\n        stake.deposit(address(bounty), C.RESOLVER_STAKE);\n        vm.prank(creator);\n        uint256 id = bounty.createBounty(\"q\", block.timestamp + 1 days, \"src\", C.MIN_REWARD);\n        vm.prank(alice);\n        bounty.forecast(id, 100); // the true value\n        vm.prank(sock);\n        bounty.forecast(id, 50);\n        vm.warp(block.timestamp + 1 days);\n        vm.prank(creator);\n        bounty.resolve(id, 50); // wrong on purpose: makes the sock the provisional winner\n        vm.prank(sock);\n        disputes.dispute(address(bounty), id, 51); // bogus correction fills the only slot\n\n        // The honest challenger can no longer dispute at all.\n        vm.prank(challenger);\n        vm.expectRevert(DisputeWindow.AlreadyDisputed.selector);\n        disputes.dispute(address(bounty), id, 100);\n\n        // The panel does its job and agrees on the real value.\n        vm.prank(C.disputeSigner1);\n        disputes.voteValue(address(bounty), id, 100);\n        vm.prank(C.disputeSigner2);\n        disputes.voteValue(address(bounty), id, 100);\n        bounty.finalize(id);\n        assertEq(bounty.getBounty(id).winner, alice);\n\n        // Settle every position the creator household holds.\n        vm.prank(sock);\n        disputes.withdrawPayout(); // 2 * BOND\n        vm.prank(sock);\n        bounty.withdrawBond(id);\n        uint256 remaining = stake.stakeOf(address(bounty), creator);\n        vm.prank(creator);\n        stake.withdraw(address(bounty), remaining);\n        uint256 householdAfter = token.balanceOf(creator) + token.balanceOf(sock);\n\n        // Fails on the current code: the household is down only the reward it would have refunded\n        // anyway. A wrong resolution that was successfully disputed cost it no slash and no bond.\n        assertGe(\n            householdBefore - householdAfter,\n            C.MIN_REWARD + (C.RESOLVER_STAKE * C.SLASH_BPS) / 10_000,\n            \"slash recovered by the resolver's own second wallet\"\n        );\n    }\n}","reproduction":"State: creator staked 25,000e18 in ResolverStake for ForecastBounty; creator created bounty id with reward 5,000e18 and deadline T; alice forecast 100 (true value); sock (creator's second wallet) forecast 50. At T: creator.resolve(id, 50) (wrong). Same block: sock.dispute(bounty, id, 51). Then challenger.dispute(bounty, id, 100) -> reverts AlreadyDisputed. disputeSigner1.voteValue(bounty, id, 100); disputeSigner2.voteValue(bounty, id, 100); finalize(id) -> winner alice. sock.withdrawPayout() -> 200e18; sock.withdrawBond(id) -> 100e18; ResolverStake.settle paid 5,000e18 slash to sock; creator.withdraw(bounty, 20,000e18). Expected per brief: the wrong resolution costs the resolver 5,000e18 of stake plus its 100e18 resolution bond. Actual: creator+sock balances fell by exactly 5,000e18 (the reward alice legitimately won) and nothing else (test test_wrongResolutionCostsTheResolverItsSlash fails on this tree with '5000e18 < 10000e18').","severity":"medium","snippet":"        if (item.disputer != address(0)) revert AlreadyDisputed();","title":"DisputeWindow: single first-come dispute slot lets the resolver self-dispute from a second wallet, recover its own slash and lock honest challengers out"},{"citation":"resolved","description":"Asymmetry (branch diff in _decide). Uncontested/Upheld pay the resolver, Overturned pays the disputer, but TimedOut refunds both sides in full and ForecastBounty._finalize maps it to state Refunded with winner = address(0) (ForecastBounty.sol:272-276), after which claim() reverts NotResolved and only the creator can take the reward back via refundReward. A disputer therefore bears no cost at all when the panel fails to reach a two-vote exact match within PANEL_TIMEOUT, while the winning forecaster loses the entire reward and the honest resolver loses nothing but time. Anyone (including the creator from a second wallet, who then recovers the reward) can convert a correct, uncontested-looking resolution into a creator refund with one 100e18 bond that is returned. The behaviour is documented in README.md ('A panel timeout also refunds the creator') and depends on the three immutable signers being unavailable or disagreeing, which is a stated trust assumption; reported as low so the requester can weigh whether a timed-out dispute should instead leave the original resolution standing (Uncontested-equivalent) or forfeit the disputer's bond to the winner.","line":185,"path":"src/DisputeWindow.sol","reproduction":"State: creator staked 25,000e18; bounty id with reward 5,000e18, alice forecast 100. At deadline creator.resolve(id, 100) (correct). challenger.dispute(bounty, id, 1) with 100e18 bond. No signer votes. After 7 days: finalize(id) -> DisputeWindow outcome TimedOut, bounty state Refunded, winner address(0). challenger.withdrawPayout() returns exactly 100e18 (balance unchanged from before the dispute). alice.claim(id) reverts NotResolved. creator.refundReward(id) succeeds and receives 5,000e18. Expected by the brief: 'the closest forecast wins the reward' and 'the losing side's bond goes to the winning side'. Actual: the correct forecaster receives nothing and the disputer paid nothing (scratch test test_timedOutDisputeRefundsCreatorForFree passes on this tree, demonstrating the behaviour).","severity":"low","snippet":"        } else if (outcome == Outcome.TimedOut) {\n            credit[item.resolver] += BOND;\n            credit[item.disputer] += BOND;","title":"Panel timeout returns the disputer's bond in full, so with an inactive panel any 100-bond dispute cancels a correct resolution for free and the winner is denied the reward"},{"citation":"resolved","description":"Access control, documented design choice. The brief's ForecastBounty section says resolve is 'callable only by an address staking at least RESOLVER_STAKE', while its summary says 'the creator resolves'. The implementation requires both: msg.sender == item.creator (line 148) and that the creator's own stake in ResolverStake for this market is >= RESOLVER_STAKE (lock at line 160). README.md states the creator-only reading was chosen deliberately. Consequence: a staked third-party resolver can never resolve, and a bounty whose creator never stakes 25,000e18 can only end in a creator refund or a permissionless expire at deadline + 7 days; participants' bonds are then locked for up to seven days with no possible payout. No bypass exists; recorded so the requester confirms the intended reading. If the broader reading is wanted, resolve must drop the creator check and the dispute/slash path already keys everything on the resolver address passed to lock/open, so no other change is needed.","line":148,"path":"src/ForecastBounty.sol","reproduction":"State: alice deposited 25,000e18 into ResolverStake for ForecastBounty; creator (with no stake) created bounty id with deadline T. At T: alice.resolve(id, 1) -> reverts NotCreator (scratch test test_stakedThirdPartyCannotResolve). Expected under the brief's ForecastBounty wording: a staked address may resolve. Actual: only the creator may, and if the creator holds no stake the bounty cannot be resolved by anyone.","severity":"info","snippet":"        if (msg.sender != item.creator) revert NotCreator();","title":"resolve() is creator-only, narrower than the brief's 'callable only by an address staking at least RESOLVER_STAKE'"}],"hash":"c54ae8ac334dd3cad0322489b46641268d34a349130ba067edc26a4ecd2a5003","nodeId":"54febdc0-2f51-4595-b117-f7de910ab8eb","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds four findings and a coverage row for every one of the 34 listed entry points plus two invariants. No source file was changed. The only new files are the findings file and three scratch tests under `test/scratch/`, which is gitignored.\n\n**What I found**\n\n- **Medium, BountySubsidy.subsidize (line 44).** The \"no other subsidised bounty open\" limiter never binds. A creator can create a bounty with a one-second deadline, take the 100 FORECAST grant, refund next block, and repeat. One wallet reusing one 5,000 reward emptied a 1,000 FORECAST pool in ten blocks. Proof test attached and fails on this tree.\n- **Medium, DisputeWindow.dispute (line 105).** Only one dispute is admitted per case and only the resolver address is excluded. A resolver who posts a wrong value disputes from a second wallet in the same block with a bogus correction. Honest challengers then revert with AlreadyDisputed, and when the panel fixes the value, the 5,000 slash and both bonds go to the resolver's own wallet. The brief's \"a wrong resolution costs them real value\" deterrent is void at gas cost. Proof test attached and fails on this tree.\n- **Low, DisputeWindow._decide TimedOut branch (line 185).** A timed-out panel refunds the disputer in full and turns the bounty into a creator refund with no winner. With an inactive panel, any 100-bond dispute cancels a correct resolution for free, and the correct forecaster gets nothing.\n- **Info, ForecastBounty.resolve (line 148).** Resolution is creator-only on top of the stake requirement, narrower than the brief's \"any address staking RESOLVER_STAKE\" wording. README documents the choice, so it is recorded for confirmation only.\n\nAll three substantive findings are acknowledged in the README as trust or Sybil limits. I reported them anyway because each has a concrete, unprivileged, zero-cost reproduction and sits exactly on the access, economics and asymmetry seam assigned to me. The judge can downgrade if the requester accepts them as design.\n\n**What holds**\n\nEvery token-moving path pulls only from `msg.sender`, so no contract can spend another's approval. Registry, stake and dispute namespaces are keyed by caller, so counterfeit markets are isolated. Bond withdrawal has no admin gate and is reachable in every terminal state. Claim and refund share one `rewardPaid` flag, so no double payout exists. All slither and aderyn leads in my area were checked and rejected as non-findings, including the divide-before-multiply slash formula, which is an exact floor.\n\n**Not reached**\n\nI did not run the mainnet fork suite or the fuzz campaign beyond the default local run, and I did not review the pool or factory interaction since no manifest exists yet.","treeHash":null,"usage":{"cachedInputTokens":1124087,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":43724,"runtime":"claude","turns":35,"wallClockMs":615385}},{"artifacts":[],"attempt":1,"bundleHash":"c5769a2b4b3beb4c7599121b4fc3e65ba9e0fc6ca8d2087bb71214778be67438","device":"d11ea2b5e05fa7a8","findings":[],"hash":"ca2287a63a26c0b615ccb58024615954f8a617983fe264b44439e4d6ae5a454b","nodeId":"10b94160-9837-49eb-b7ec-d3e143e5d0d3","outcome":"completed","summary":"launch.json is written and verified. Only that file changed in the working tree.\n\n**What the manifest contains**\n\n- Token: LaunchToken, name \"Forecast Bounty\", symbol FORECAST, 18 decimals, no constructor arguments.\n- Six application contracts in constructor dependency order: BountyRegistry (no arguments), DisputeWindow, ResolverStake, ForecastBounty, BountySubsidy, ResolverBootstrap.\n- Every constructor argument matches the accepted source and DeploymentConfig: bond 100e18, dispute window 172800 seconds, resolver stake 25,000e18, slash 2000 bps, min reward 5,000e18, max entries 500, and the three brief-given dispute signers in lowercase.\n- Contract references all point backward and only fill address parameters. No $owner appears because no contract has an owner, matching the brief's no-owner rule and the deployment tests.\n- Pool fields are exactly as the launch states: IMD pair address, fee 12500, tick spacing 60, the one-to-one initial price.\n- Notes explain each contract, that the subsidy and bootstrap pools start empty and are funded by the requester after launch, and that supply split fields are not manifest fields.\n\n**Checks run**\n\n- A script cross-checked each constructorArgs list against the compiled ABI: arity, argument types, reference direction, and address formatting all match.\n- `forge build` succeeds (lint warnings only, pre-existing).\n- `forge test` with the verifier's fuzz seed: 64 tests passed, 0 failed.","treeHash":"135ada7fbfdaea479b99e516c25462d405c01309","usage":{"cachedInputTokens":306581,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":5320,"runtime":"claude","turns":7,"wallClockMs":110346}},{"artifacts":[],"attempt":1,"bundleHash":"b77d32e6a3b9039ca0df44846bf95fe7d9030b65f9f16f81ec393dc47b4b515f","device":"e6de8d4c6cf97551","findings":[{"description":"The brief says BountySubsidy pays a creator only when the creator has no other subsidised bounty open. subsidize() frees the slot only when the previous subsidised bounty is Finalized with at least one entry. A subsidised bounty that ends Refunded (expired with no resolution) or is Finalized with zero entries is no longer open, yet it blocks every future grant to that creator. The code comment calls this deliberate anti-farming. It is still stricter than the brief, and it contradicts README.md line 121 ('Finalization or cancellation frees the slot'), though README line 126 states the stricter rule.","line":47,"path":"src/BountySubsidy.sol","reproduction":"Creator creates bounty 1 (reward 5,000e18) and calls subsidize(1), receiving 100e18. Nobody forecasts; after deadline + RESOLUTION_TIMEOUT the creator calls refundReward(1), so bounty 1 is Refunded, not open. Creator creates bounty 2 (reward 5,000e18) and calls subsidize(2). Expected per brief: grant of 100e18. Actual: reverts OtherSubsidisedBountyOpen.","severity":"low","title":"Refunded or expired subsidised bounty holds the creator's subsidy slot forever"}],"hash":"e5a438dc871cc21e0dc2a34c6a6427b1b98d1bfc111f0dc8989c83f8fa9a8e14","nodeId":"6f8bc2b2-733e-4c01-9ca7-bbc8e400609c","outcome":"completed","summary":"All tests pass: `forge build` and `forge test` with the verifier's seed ran 81 tests across 13 suites with no failures.\n\n**What changed since last round:** the implementer fixed both slash-routing defects I reported in `ForecastBounty._finalize`:\n- when the creator's own entry was the corrected winner, the slash used to go back to the resolver;\n- an overturned bounty with no entries was never slashed.\n\nNo other findings came with this revision, so I kept the existing tests as they were and changed two files under `test/`:\n\n- **`test/LifecycleProperties.t.sol`**: one new fuzz test, `testFuzzOverturnSlashRoutingAndConservation`, set to 1,000 runs. Each run picks a random set of forecasters (sometimes including the creator), small values so ties and creator wins come up often, and a panel value that may or may not match the disputer's. Every run checks that:\n  - the resolver loses exactly 20% of the required stake;\n  - the slash goes to the right wallet: the disputer if the panel agreed with them; otherwise the winner under the panel's value, worked out by the test itself; or the disputer again if that winner is the resolver or there are no entries;\n  - no other wallet's balance moves, and the disputer gets back both bonds;\n  - every contract still holds exactly what it owes.\n\n  I ran it against the pre-fix `ForecastBounty.sol` to make sure it catches the old bug. It failed there (every forecast 0 with the creator entering, resolver says 17, disputer 25, panel 0, and the slash went back to the creator). Then I restored the current file.\n- **`test/ADDED_COVERAGE.md`**: the \"Reported, not asserted\" section now says both defects are fixed and names the test that covers them.\n\n**Still reported (low), in `.imd-findings.json`:** if a creator's subsidised bounty is refunded, or finalized with no entries, that creator can never get another subsidy. The brief only blocks a creator who has another subsidised bounty still *open*. The code calls this deliberate, to stop creators farming grants by creating and refunding bounties. `README.md` contradicts itself on this: line 121 says finalization or cancellation frees the slot, while line 126 gives the stricter rule the code follows.\n\nThe suite has not been run against a mainnet fork.","treeHash":"c19dee7238674dc560bc9c93669581f8ebe05acf","usage":{"cachedInputTokens":1092060,"inputTokens":30,"model":"claude-opus-5-5","outputTokens":10289,"runtime":"claude","turns":15,"wallClockMs":222656}},{"artifacts":[],"attempt":1,"bundleHash":"fbe2c353d329c5177ab7fe39ec52cde304bbc9170c535a7b01da9aded2e69156","device":"f009de0ce52c7b3a","findings":[],"hash":"fe0e5e77a1556deaba4d10567a599a5192a1cdcfb2558230a2e723bab89e8923","nodeId":"10b94160-9837-49eb-b7ec-d3e143e5d0d3","outcome":"completed","summary":"The manifest needed no change. launch.json already describes the accepted tree exactly, and I verified it rather than rewriting it.\n\n**What I checked**\n\n- Every constructor in src matched against the manifest, using the compiled ABIs from forge build. All six application contracts are listed in dependency order with one argument per parameter: BountyRegistry (no arguments), DisputeWindow, ResolverStake, ForecastBounty, BountySubsidy, ResolverBootstrap.\n- Numeric arguments equal the DeploymentConfig constants: MIN_REWARD 5,000e18, BOND 100e18, RESOLVER_STAKE 25,000e18, SLASH_BPS 2000, DISPUTE_WINDOW 172800 seconds, MAX_ENTRIES 500.\n- The three dispute signers are the brief's addresses, lowercase, non-zero, and distinct. Every `$contract:` reference points backward, and references sit only in address-typed parameters.\n- No `$owner` appears because no contract has an owner, matching the Owners protected test.\n- Token block is LaunchToken, \"Forecast Bounty\", FORECAST, 18 decimals. Pool block is the IMD pair address, fee 12500, tickSpacing 60, and the given initialPrice. Only schema keys are present, and notes are 2,301 characters.\n\n**Test results**\n\n```\nRan 9 test suites: 67 tests passed, 0 failed, 0 skipped\n```\n\nThat run used the verifier's fuzz seed. The working tree is clean, so no file other than launch.json is touched, and launch.json itself is byte-identical to the accepted version. The scratch validation script lives in test/scratch and is not submitted.","treeHash":"d32afaf8bfa2a6076acba3af48d7adbbe2fef63b","usage":{"cachedInputTokens":249208,"inputTokens":162,"model":"claude-fable-5-1","outputTokens":4810,"runtime":"claude","turns":6,"wallClockMs":122418}}],"verification":[{"checks":[{"durationMs":3920,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.83s\nCompiler run successful!\n","passed":true},{"durationMs":6573,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339282)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 35.56ms (2.06ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805435)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393081)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904854, ~: 1904898)\nLogs:\n  Bound result 975000000000000000001875\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493254)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 35.49ms (39.51ms CPU time)\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976691)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 45.08ms (1.22ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89088, ~: 89138)\nLogs:\n  Bound result 162514264337593543871106\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 52.86ms (5.26ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13052823)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15106119)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 52.85ms (24.83ms CPU time)\n\nRan 13 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659649)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131248)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006219)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532600)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503376, ~: 2514566)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6666)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310202)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652231)\n[PASS] testPanelTimeoutReturnsBondsCancelsRewardAndReleasesStake() (gas: 3036899)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681134)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266753)\n[PASS] testThreeDifferentPanelValuesTimeOutWithoutPickingAnArbitraryWinner() (gas: 2554229)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 52.90ms (62.18ms CPU time)\n\nRan 9 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5516911)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3762816)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3463067)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416578, ~: 416350)\nLogs:\n  Bound result 5229\n  Bound result 378\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910647)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535462)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedBountyReleasesSubsidySlotButNoDuplicateGrant() (gas: 1607295)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124868)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 90.09ms (19.74ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925781)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588342, ~: 1589052)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779218)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829193)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926010)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 90.26ms (130.77ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.48s (6.48s CPU time)\n\nRan 9 test suites in 6.48s (6.94s CPU time): 64 tests passed, 0 failed, 0 skipped (64 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":51,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":321,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":217,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1459,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/ResolverStake.sol:69: ResolverStake.lock(uint256,address) (src/ResolverStake.sol#69-80) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/lib/DonationPool.sol:24: DonationPool.deposit() (src/lib/DonationPool.sol#24-28) uses a dangerous strict equality:\n[medium/medium] unused-return at src/ForecastBounty.sol:264: ForecastBounty._finalize(uint256,ForecastBounty.Bounty) (src/ForecastBounty.sol#264-287) ignores return value by resolverStake.settle(bountyId,overturned,decision.disputer) (src/ForecastBounty.sol#286)\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:210: Reentrancy in ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:181: Reentrancy in ForecastBounty.claim(uint256) (src/ForecastBounty.sol#181-193):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:196: Reentrancy in ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208):\n[low/medium] timestamp at src/ForecastBounty.sol:146: ForecastBounty.resolve(uint256,int256) (src/ForecastBounty.sol#146-166) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:145: DisputeWindow.finalize(address,uint256) (src/DisputeWindow.sol#145-157) uses timestamp for comparisons\n[low/medium] timestamp at src/BountySubsidy.sol:35: BountySubsidy.subsidize(uint256) (src/BountySubsidy.sol#35-50) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:102: DisputeWindow.dispute(address,uint256,int256) (src/DisputeWindow.sol#102-114) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:131: ForecastBounty.forecast(uint256,int256) (src/ForecastBounty.sol#131-143) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:127: DisputeWindow._vote(address,uint256,DisputeWindow.Case,int256) (src/DisputeWindow.sol#127-142) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:108: ForecastBounty.createBounty(string,uint256,string,uint256) (src/ForecastBounty.sol#108-129) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:210: ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225) uses timestamp for comparisons\n[low/medium] timestamp at src/BountyRegistry.sol:42: BountyRegistry.append(address,bytes32,uint256,uint256) (src/BountyRegistry.sol#42-51) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:228: ForecastBounty.expire(uint256) (src/ForecastBounty.sol#228-232) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:196: ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208) uses timestamp for comparisons","passed":true},{"durationMs":342,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/BountySubsidy.sol:36: Reentrancy: State change after external call (7 places)\n[low] large-numeric-literal at src/ResolverStake.sol:37: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/ForecastBounty.sol:304: Literal Instead of Constant (6 places)\n[low] unchecked-return at src/ForecastBounty.sol:239: Unchecked Return (2 places)\n[low] uninitialized-local-variable at src/ForecastBounty.sol:306: Uninitialized Local Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"04055a52cacefc8a0ffc326c71673d81c233b3f68d77b22e66a539f30950acb0","verifiedTreeHash":"915602c7c9718bfb0bae7af014c6400c417cd9a9","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":5149,"exitCode":0,"name":"build","output":"Compiling 59 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.06s\nCompiler run successful!\n","passed":true},{"durationMs":8906,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13052823)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15106119)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 26.50ms (26.59ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89088, ~: 89138)\nLogs:\n  Bound result 1874\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 37.29ms (36.39ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805435)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393081)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904854, ~: 1904898)\nLogs:\n  Bound result 637514264337593543869075\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493254)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 156.25ms (65.39ms CPU time)\n\nRan 9 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5516911)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3762816)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3463067)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416578, ~: 416350)\nLogs:\n  Bound result 5229\n  Bound result 378\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910647)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535462)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedBountyReleasesSubsidySlotButNoDuplicateGrant() (gas: 1607295)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124868)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 159.77ms (41.63ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339282)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 160.75ms (2.23ms CPU time)\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976691)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 160.72ms (1.31ms CPU time)\n\nRan 5 tests for test/ComponentIsolation.t.sol:ComponentIsolationTest\n[PASS] testBootstrapRejectsWrongAssetOrStakeDestinationWithoutConsumingEligibility() (gas: 2506289)\n[PASS] testRegistryCompleteTransitionMatrixPreservesHistoryAndOriginalTerms() (gas: 14386872)\n[PASS] testResolutionRejectsForeignAssetOrUnequalArbitrationBondWithoutPartialState() (gas: 1988587)\n[PASS] testSubsidyRejectsWrongTokenOrThresholdWithoutConsumingGrant() (gas: 874651)\n[PASS] testTwoRealMarketsShareComponentsButNeverForecastsStakeOrCases() (gas: 3972821)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 164.25ms (11.86ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925781)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588359, ~: 1589052)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779218)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829193)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926010)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 164.31ms (227.74ms CPU time)\n\nRan 13 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659649)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131248)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006219)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532600)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503376, ~: 2514566)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6666)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310202)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652231)\n[PASS] testPanelTimeoutReturnsBondsCancelsRewardAndReleasesStake() (gas: 3036899)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681134)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266753)\n[PASS] testThreeDifferentPanelValuesTimeOutWithoutPickingAnArbitraryWinner() (gas: 2554229)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 165.57ms (71.31ms CPU time)\n\nRan 4 tests for test/LifecycleProperties.t.sol:LifecyclePropertiesTest\n[PASS] testEveryForecastIdenticalIncludingSignedMinimumStillHasAWinner() (gas: 3083019)\n[PASS] testFuzzFiveEntrantsAllBondsRecoverAndFinalWealthIsConserved(int256[5],int256,int256,uint96,uint8,uint8) (runs: 1000, μ: 4221086, ~: 4149875)\nLogs:\n  Bound result 495005903467573485010785\n\n[PASS] testOneWeiShortRewardAndBondAllowancesFailAtomically() (gas: 1328525)\n[PASS] testResolutionEventIncludesTruthAndCorrectIndexedWinner() (gas: 1261873)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 174.91ms (175.33ms CPU time)\n\nRan 1 test for test/DonationLedgerInvariant.t.sol:DonationLedgerInvariantTest\n[PASS] invariantDonationsCannotBeDoubleCreditedOrReleasedWithoutBacking() (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------------+-------------------+-------+---------+----------╮\n| Contract              | Selector          | Calls | Reverts | Discards |\n+========================================================================+\n| DonationLedgerHandler | bootstrapResolver | 2789  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | deposit           | 2716  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | replaceOpenBounty | 2671  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | subsidize         | 2731  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | sync              | 2722  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | transferDirectly  | 2755  | 0       | 0        |\n╰-----------------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 200000000000000000000\n  Bound result 199999999999999999999\n  Bound result 1\n  Bound result 1\n  Bound result 96\n  Bound result 800\n  Bound result 0\n  Bound result 5000\n  Bound result 5847\n  Bound result 506855240656628070237\n  Bound result 899868739220204263244\n  Bound result 2252\n  Bound result 13000000000000000001\n  Bound result 12\n  Bound result 8\n  Bound result 999999999999999999976\n  Bound result 10707\n  Bound result 123\n  Bound result 899868739220204263244\n  Bound result 100\n  Bound result 181766576533515212728\n  Bound result 49\n  Bound result 188676694209610372944\n  Bound result 13000000000000000000\n  Bound result 4517\n  Bound result 1098\n  Bound result 899999999999999998938\n  Bound result 1438\n  Bound result 4967\n  Bound result 1543753844\n  Bound result 959666594510983519146\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.65s (2.64s CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.59s (6.58s CPU time)\n\nRan 1 test for test/StakeLedgerInvariant.t.sol:StakeLedgerInvariantTest\n[PASS] invariantIndependentLedgerAcrossMarketsAndActors() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------------+---------------------+-------+---------+----------╮\n| Contract           | Selector            | Calls | Reverts | Discards |\n+=======================================================================+\n| StakeLedgerHandler | deposit             | 4122  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | donate              | 4073  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | lock                | 4064  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | replayOrCrossMarket | 4123  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | settle              | 4166  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | withdraw            | 4028  | 0       | 0        |\n╰--------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 2393\n  Bound result 1\n  Bound result 3\n  Bound result 3\n  Bound result 18\n  Bound result 2\n  Bound result 13296\n  Bound result 2\n  Bound result 3\n  Bound result 1\n  Bound result 2824\n  Bound result 1\n  Bound result 2304248604481655103721\n  Bound result 1\n  Bound result 4451\n  Bound result 2\n  Bound result 3\n  Bound result 5196\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 1\n  Bound result 2000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 4761\n  Bound result 1\n  Bound result 255\n  Bound result 2\n  Bound result 501\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 1034\n  Bound result 261146084642026555008\n  Bound result 2\n  Bound result 1\n  Bound result 18\n  Bound result 8583\n  Bound result 37886277137558400841677\n  Bound result 1\n  Bound result 434895301792413441634\n  Bound result 1\n  Bound result 11779502142297\n  Bound result 1\n  Bound result 7856\n  Bound result 1\n  Bound result 1\n  Bound result 27947855252804726804563\n  Bound result 0\n  Bound result 2\n  Bound result 11019\n  Bound result 2\n  Bound result 3433\n  Bound result 1\n  Bound result 1\n  Bound result 13786992319657006856611\n  Bound result 1048576\n  Bound result 1\n  Bound result 2\n  Bound result 999999999999999999981\n  Bound result 2\n  Bound result 26553249422127987000497\n  Bound result 13000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 259200\n  Bound result 1\n  Bound result 3\n  Bound result 1\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.80s (8.80s CPU time)\n\nRan 13 test suites in 8.81s (19.41s CPU time): 75 tests passed, 0 failed, 0 skipped (75 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":51,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/ADDED_COVERAGE.md\":29,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/ComponentIsolation.t.sol\":207,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":321,\"test/DonationLedgerInvariant.t.sol\":204,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":217,\"test/LifecycleProperties.t.sol\":198,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139,\"test/StakeLedgerInvariant.t.sol\":250},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0f2fade3417427da3bcd740245acff3fb7201641af6b8f9c38286482098341fb","verifiedTreeHash":"54d631c590bf249df6fa2a1ad53f07fed6a7d642","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":4159,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.05s\nCompiler run successful!\n","passed":true},{"durationMs":6904,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517255)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763002)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690914)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416609, ~: 416372)\nLogs:\n  Bound result 5198\n  Bound result 2656\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947441)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 16.73ms (25.94ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13087053)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15138218)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 31.21ms (31.32ms CPU time)\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976784)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 32.64ms (1.80ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89064, ~: 89138)\nLogs:\n  Bound result 162514264337593543871106\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 36.18ms (6.74ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805621)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393706)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904982, ~: 1905023)\nLogs:\n  Bound result 975000000000000000001875\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493379)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 41.00ms (48.69ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339468)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 49.04ms (4.80ms CPU time)\n\nRan 17 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337255)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2660158)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131364)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006199)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2533153)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503451, ~: 2514625)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6721)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310283)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652334)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038424)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681305)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453967)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266857)\n[PASS] testThirdValueOverturnNeverPaysSlashToTheResolverItself() (gas: 2334033)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2492110)\n[PASS] testThirdValueOverturnWithNoEntriesStillSlashesToDisputer() (gas: 2159996)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585640)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 100.22ms (74.92ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925874)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588363, ~: 1589066)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779311)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829286)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926196)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 100.24ms (140.26ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.80s (6.79s CPU time)\n\nRan 9 test suites in 6.80s (7.20s CPU time): 69 tests passed, 0 failed, 0 skipped (69 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"launch.json\":69,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":321,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":406,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"58b061604ae59fd97dd0c6651f04d38063acf8a28cf05b55e869ee47735c261b","verifiedTreeHash":"54411cbc2cf636cefddd95f8bd592d720254c8f7","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":5361,"exitCode":0,"name":"build","output":"Compiling 59 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.27s\nCompiler run successful!\n","passed":true},{"durationMs":9613,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339500)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 4.43ms (4.39ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13076468)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15129869)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 43.26ms (42.49ms CPU time)\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976800)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 157.75ms (1.33ms CPU time)\n\nRan 5 tests for test/ComponentIsolation.t.sol:ComponentIsolationTest\n[PASS] testBootstrapRejectsWrongAssetOrStakeDestinationWithoutConsumingEligibility() (gas: 2506398)\n[PASS] testRegistryCompleteTransitionMatrixPreservesHistoryAndOriginalTerms() (gas: 14386872)\n[PASS] testResolutionRejectsForeignAssetOrUnequalArbitrationBondWithoutPartialState() (gas: 1988587)\n[PASS] testSubsidyRejectsWrongTokenOrThresholdWithoutConsumingGrant() (gas: 874651)\n[PASS] testTwoRealMarketsShareComponentsButNeverForecastsStakeOrCases() (gas: 3973100)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 171.77ms (13.78ms CPU time)\n\nRan 15 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659905)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131402)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006243)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532922)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503472, ~: 2514631)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6689)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310289)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652380)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038452)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681300)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453768)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266913)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2491879)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585668)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 181.42ms (84.07ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89090, ~: 89150)\nLogs:\n  Bound result 162514264337593543871106\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 190.63ms (189.05ms CPU time)\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517332)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763034)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690930)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416603, ~: 416372)\nLogs:\n  Bound result 129706696399857062819504\n  Bound result 4324560441759062235\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947457)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 190.64ms (177.06ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805653)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393931)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1905027, ~: 1905068)\nLogs:\n  Bound result 975000064915284163135255\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493424)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 190.85ms (79.76ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925890)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588387, ~: 1589072)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779327)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829302)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926228)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 190.86ms (206.21ms CPU time)\n\nRan 4 tests for test/LifecycleProperties.t.sol:LifecyclePropertiesTest\n[PASS] testEveryForecastIdenticalIncludingSignedMinimumStillHasAWinner() (gas: 3083128)\n[PASS] testFuzzFiveEntrantsAllBondsRecoverAndFinalWealthIsConserved(int256[5],int256,int256,uint96,uint8,uint8) (runs: 1000, μ: 4314082, ~: 4428171)\nLogs:\n  Bound result 495000000000000999505620\n\n[PASS] testOneWeiShortRewardAndBondAllowancesFailAtomically() (gas: 1328525)\n[PASS] testResolutionEventIncludesTruthAndCorrectIndexedWinner() (gas: 1261873)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 190.97ms (190.73ms CPU time)\n\nRan 1 test for test/DonationLedgerInvariant.t.sol:DonationLedgerInvariantTest\n[PASS] invariantDonationsCannotBeDoubleCreditedOrReleasedWithoutBacking() (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------------+-------------------+-------+---------+----------╮\n| Contract              | Selector          | Calls | Reverts | Discards |\n+========================================================================+\n| DonationLedgerHandler | bootstrapResolver | 2770  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | deposit           | 2696  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | replaceOpenBounty | 2751  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | subsidize         | 2741  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | sync              | 2731  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | transferDirectly  | 2695  | 0       | 0        |\n╰-----------------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 200000000000000000000\n  Bound result 199999999999999999999\n  Bound result 1\n  Bound result 1\n  Bound result 1000\n  Bound result 797955657607522354476\n  Bound result 1048576\n  Bound result 1000000000000000000000\n  Bound result 7636\n  Bound result 1048576\n  Bound result 99\n  Bound result 575006126323873809953\n  Bound result 999999999999999999956\n  Bound result 2531\n  Bound result 10806\n  Bound result 9479\n  Bound result 18000000000000000000\n  Bound result 2714\n  Bound result 32\n  Bound result 933493616555517276237\n  Bound result 2816\n  Bound result 999999999999999900001\n  Bound result 3159\n  Bound result 4189034727\n  Bound result 995867871798503382717\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.00s (2.99s CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.98s (6.97s CPU time)\n\nRan 1 test for test/StakeLedgerInvariant.t.sol:StakeLedgerInvariantTest\n[PASS] invariantIndependentLedgerAcrossMarketsAndActors() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------------+---------------------+-------+---------+----------╮\n| Contract           | Selector            | Calls | Reverts | Discards |\n+=======================================================================+\n| StakeLedgerHandler | deposit             | 4122  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | donate              | 4073  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | lock                | 4064  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | replayOrCrossMarket | 4123  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | settle              | 4166  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | withdraw            | 4028  | 0       | 0        |\n╰--------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 2393\n  Bound result 1\n  Bound result 3\n  Bound result 3\n  Bound result 8000000\n  Bound result 2\n  Bound result 1\n  Bound result 2\n  Bound result 3\n  Bound result 11178\n  Bound result 5823\n  Bound result 1\n  Bound result 2304248604481655103721\n  Bound result 1\n  Bound result 4453\n  Bound result 2\n  Bound result 2\n  Bound result 999999999999999990784\n  Bound result 1\n  Bound result 2\n  Bound result 2\n  Bound result 1\n  Bound result 7000000000000000000\n  Bound result 2\n  Bound result 3\n  Bound result 4776\n  Bound result 1\n  Bound result 255\n  Bound result 2\n  Bound result 21\n  Bound result 2\n  Bound result 7\n  Bound result 3\n  Bound result 1\n  Bound result 3661\n  Bound result 261146084642026555008\n  Bound result 2\n  Bound result 2\n  Bound result 1049075\n  Bound result 8664\n  Bound result 37886277137558400841677\n  Bound result 1\n  Bound result 434895301792413441634\n  Bound result 1\n  Bound result 11779502142297\n  Bound result 1\n  Bound result 7980\n  Bound result 1\n  Bound result 2\n  Bound result 27947855252804726804563\n  Bound result 24726999999999999999922\n  Bound result 2\n  Bound result 11264\n  Bound result 2\n  Bound result 3529\n  Bound result 3\n  Bound result 3\n  Bound result 13786992319657006856611\n  Bound result 77\n  Bound result 2972204662\n  Bound result 3\n  Bound result 999999999999999999981\n  Bound result 5\n  Bound result 26553249422127987000497\n  Bound result 13000000000000000000\n  Bound result 1\n  Bound result 4\n  Bound result 6\n  Bound result 259201\n  Bound result 1\n  Bound result 6\n  Bound result 1\n  Bound result 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.52s (9.52s CPU time)\n\nRan 13 test suites in 9.52s (21.01s CPU time): 78 tests passed, 0 failed, 0 skipped (78 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/ADDED_COVERAGE.md\":37,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/ComponentIsolation.t.sol\":207,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":355,\"test/DonationLedgerInvariant.t.sol\":235,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/LifecycleProperties.t.sol\":195,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139,\"test/StakeLedgerInvariant.t.sol\":250},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6358c78cfdf0a46317bf424c7afae0cdda5fddfa8ae24fac47dbe274db7d1192","verifiedTreeHash":"abf3c722de21e7faff46d5b13eef34749351a653","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3755,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.67s\nCompiler run successful!\n","passed":true},{"durationMs":6424,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976784)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.97ms (1.36ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89064, ~: 89138)\nLogs:\n  Bound result 96006865738022423100315\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 12.90ms (11.68ms CPU time)\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517255)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763002)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690914)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416609, ~: 416372)\nLogs:\n  Bound result 802094582697849405024998\n  Bound result 3\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947441)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 21.51ms (37.69ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13087053)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15138218)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 26.43ms (26.70ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339468)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 42.40ms (2.14ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805621)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393706)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904982, ~: 1905023)\nLogs:\n  Bound result 621006865738022423100314\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493379)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 56.73ms (48.60ms CPU time)\n\nRan 17 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337255)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2660158)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131364)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006199)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2533153)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503451, ~: 2514625)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6721)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310283)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652334)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038424)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681305)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453967)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266857)\n[PASS] testThirdValueOverturnNeverPaysSlashToTheResolverItself() (gas: 2334033)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2492110)\n[PASS] testThirdValueOverturnWithNoEntriesStillSlashesToDisputer() (gas: 2159996)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585640)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 57.18ms (70.18ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925874)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588363, ~: 1589066)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779311)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829286)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926196)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 87.47ms (145.87ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.33s (6.33s CPU time)\n\nRan 9 test suites in 6.33s (6.64s CPU time): 69 tests passed, 0 failed, 0 skipped (69 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":321,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":406,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1452,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/ResolverStake.sol:69: ResolverStake.lock(uint256,address) (src/ResolverStake.sol#69-80) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/lib/DonationPool.sol:24: DonationPool.deposit() (src/lib/DonationPool.sol#24-28) uses a dangerous strict equality:\n[medium/medium] unused-return at src/ForecastBounty.sol:264: ForecastBounty._finalize(uint256,ForecastBounty.Bounty) (src/ForecastBounty.sol#264-293) ignores return value by resolverStake.settle(bountyId,overturned,slashTo) (src/ForecastBounty.sol#292)\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:196: Reentrancy in ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:210: Reentrancy in ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:181: Reentrancy in ForecastBounty.claim(uint256) (src/ForecastBounty.sol#181-193):\n[low/medium] timestamp at src/ForecastBounty.sol:146: ForecastBounty.resolve(uint256,int256) (src/ForecastBounty.sol#146-166) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:145: DisputeWindow.finalize(address,uint256) (src/DisputeWindow.sol#145-157) uses timestamp for comparisons\n[low/medium] timestamp at src/BountySubsidy.sol:35: BountySubsidy.subsidize(uint256) (src/BountySubsidy.sol#35-55) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:102: DisputeWindow.dispute(address,uint256,int256) (src/DisputeWindow.sol#102-114) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:131: ForecastBounty.forecast(uint256,int256) (src/ForecastBounty.sol#131-143) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:127: DisputeWindow._vote(address,uint256,DisputeWindow.Case,int256) (src/DisputeWindow.sol#127-142) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:108: ForecastBounty.createBounty(string,uint256,string,uint256) (src/ForecastBounty.sol#108-129) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:210: ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225) uses timestamp for comparisons\n[low/medium] timestamp at src/BountyRegistry.sol:42: BountyRegistry.append(address,bytes32,uint256,uint256) (src/BountyRegistry.sol#42-51) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:228: ForecastBounty.expire(uint256) (src/ForecastBounty.sol#228-232) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:196: ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208) uses timestamp for comparisons","passed":true},{"durationMs":344,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/BountySubsidy.sol:36: Reentrancy: State change after external call (7 places)\n[low] large-numeric-literal at src/ResolverStake.sol:37: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/ForecastBounty.sol:310: Literal Instead of Constant (6 places)\n[low] unchecked-return at src/ForecastBounty.sol:239: Unchecked Return (2 places)\n[low] uninitialized-local-variable at src/ForecastBounty.sol:312: Uninitialized Local Variable","passed":true},{"durationMs":1583,"exitCode":0,"name":"proof ac00696a61b7","output":"Compiling 45 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.04s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-e8592ea8/Proof_ac00696a61b7.t.sol:ProofResolverSelfSlash\n[PASS] testOverturnedResolverLosesItsSlash() (gas: 2028980)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.20ms (395.42µs CPU time)\n\nRan 1 test suite in 3.50ms (1.20ms 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":"87a4100a1083847f3152019230dc13db44085b6de486905149d7f7fed62c890c","verifiedTreeHash":"84221cc4ad15ff12a8b52fba286950c62a6ef644","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3902,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.81s\nCompiler run successful!\n","passed":true},{"durationMs":6817,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976800)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.40ms (1.09ms CPU time)\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517332)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763034)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690930)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416603, ~: 416372)\nLogs:\n  Bound result 1\n  Bound result 39697216372694171177\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947457)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 43.63ms (22.44ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13076468)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15129869)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 43.71ms (26.78ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805653)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393931)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1905027, ~: 1905068)\nLogs:\n  Bound result 975000064915284163135255\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493424)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 43.66ms (48.45ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339500)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 56.24ms (1.87ms CPU time)\n\nRan 15 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659905)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131402)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006243)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532922)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503471, ~: 2514631)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6689)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310289)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652380)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038452)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681300)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453768)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266913)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2491879)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585668)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 56.72ms (68.90ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89090, ~: 89150)\nLogs:\n  Bound result 96006865738022423100315\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 86.94ms (6.85ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925890)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588381, ~: 1589087)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779327)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829302)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926228)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 86.99ms (138.47ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.72s (6.72s CPU time)\n\nRan 9 test suites in 6.73s (7.15s CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":65,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":355,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1475,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/ResolverStake.sol:69: ResolverStake.lock(uint256,address) (src/ResolverStake.sol#69-80) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/lib/DonationPool.sol:24: DonationPool.deposit() (src/lib/DonationPool.sol#24-28) uses a dangerous strict equality:\n[medium/medium] unused-return at src/ForecastBounty.sol:264: ForecastBounty._finalize(uint256,ForecastBounty.Bounty) (src/ForecastBounty.sol#264-287) ignores return value by resolverStake.settle(bountyId,overturned && slashTo != address(0),slashTo) (src/ForecastBounty.sol#286)\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:210: Reentrancy in ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:181: Reentrancy in ForecastBounty.claim(uint256) (src/ForecastBounty.sol#181-193):\n[low/medium] reentrancy-benign at src/ForecastBounty.sol:196: Reentrancy in ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208):\n[low/medium] timestamp at src/ForecastBounty.sol:146: ForecastBounty.resolve(uint256,int256) (src/ForecastBounty.sol#146-166) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:145: DisputeWindow.finalize(address,uint256) (src/DisputeWindow.sol#145-157) uses timestamp for comparisons\n[low/medium] timestamp at src/BountySubsidy.sol:35: BountySubsidy.subsidize(uint256) (src/BountySubsidy.sol#35-55) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:102: DisputeWindow.dispute(address,uint256,int256) (src/DisputeWindow.sol#102-114) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:131: ForecastBounty.forecast(uint256,int256) (src/ForecastBounty.sol#131-143) uses timestamp for comparisons\n[low/medium] timestamp at src/DisputeWindow.sol:127: DisputeWindow._vote(address,uint256,DisputeWindow.Case,int256) (src/DisputeWindow.sol#127-142) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:108: ForecastBounty.createBounty(string,uint256,string,uint256) (src/ForecastBounty.sol#108-129) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:210: ForecastBounty.refundReward(uint256) (src/ForecastBounty.sol#210-225) uses timestamp for comparisons\n[low/medium] timestamp at src/BountyRegistry.sol:42: BountyRegistry.append(address,bytes32,uint256,uint256) (src/BountyRegistry.sol#42-51) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:228: ForecastBounty.expire(uint256) (src/ForecastBounty.sol#228-232) uses timestamp for comparisons\n[low/medium] timestamp at src/ForecastBounty.sol:196: ForecastBounty.withdrawBond(uint256) (src/ForecastBounty.sol#196-208) uses timestamp for comparisons","passed":true},{"durationMs":401,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/BountySubsidy.sol:36: Reentrancy: State change after external call (7 places)\n[low] large-numeric-literal at src/ResolverStake.sol:37: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/ForecastBounty.sol:304: Literal Instead of Constant (6 places)\n[low] unchecked-return at src/ForecastBounty.sol:239: Unchecked Return (2 places)\n[low] uninitialized-local-variable at src/ForecastBounty.sol:306: Uninitialized Local Variable","passed":true},{"durationMs":1547,"exitCode":0,"name":"proof c991df634a77","output":"Compiling 45 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.05s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-e67da426/Proof_c991df634a77.t.sol:SubsidyDrainTest\n[PASS] testOneRewardOfCapitalDrainsTheWholeSubsidyPoolInTenBlocks() (gas: 6527485)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.93ms (1.16ms CPU time)\n\nRan 1 test suite in 2.78ms (1.93ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1456,"exitCode":0,"name":"proof ed99a0d15887","output":"2026-10-17T09:51:21.053166Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-tsH80t/repo/test/imd-proof-e67da426/Proof_c991df634a77.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 896.50ms\nCompiler run successful!\n\nRan 1 test for test/imd-proof-301fe5aa/Proof_ed99a0d15887.t.sol:SelfDisputeTest\n[PASS] test_wrongResolutionCostsTheResolverItsSlash() (gas: 2350823)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.31ms (455.16µs CPU time)\n\nRan 1 test suite in 2.64ms (1.31ms 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":"c2fee6ae60d8483cd6f42204863299138b1eb64b7627153e7f4e60f4cafc854b","verifiedTreeHash":"62649b6244120508426f94d24324a8980648615b","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3601,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.52s\nCompiler run successful!\n","passed":true},{"durationMs":6416,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976691)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.09ms (1.08ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89088, ~: 89138)\nLogs:\n  Bound result 1874\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.92ms (6.00ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339282)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 20.99ms (1.61ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13052823)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15106119)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 27.64ms (27.78ms CPU time)\n\nRan 9 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5516911)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3762816)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3463067)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416578, ~: 416350)\nLogs:\n  Bound result 5229\n  Bound result 378\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910647)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535462)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedBountyReleasesSubsidySlotButNoDuplicateGrant() (gas: 1607295)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124868)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 58.83ms (26.01ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805435)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393081)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904854, ~: 1904898)\nLogs:\n  Bound result 975000000000000000001875\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493254)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 59.45ms (63.58ms CPU time)\n\nRan 13 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659649)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131248)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006219)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532600)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503376, ~: 2514566)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6666)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310202)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652231)\n[PASS] testPanelTimeoutReturnsBondsCancelsRewardAndReleasesStake() (gas: 3036899)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681134)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266753)\n[PASS] testThreeDifferentPanelValuesTimeOutWithoutPickingAnArbitraryWinner() (gas: 2554229)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 61.24ms (72.31ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925781)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588342, ~: 1589052)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779218)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829193)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926010)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 100.38ms (146.87ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.32s (6.32s CPU time)\n\nRan 9 test suites in 6.32s (6.66s CPU time): 64 tests passed, 0 failed, 0 skipped (64 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"launch.json\":69,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":51,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":321,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":217,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ca2287a63a26c0b615ccb58024615954f8a617983fe264b44439e4d6ae5a454b","verifiedTreeHash":"135ada7fbfdaea479b99e516c25462d405c01309","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":6139,"exitCode":0,"name":"build","output":"Compiling 59 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.03s\nCompiler run successful!\n","passed":true},{"durationMs":10154,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976784)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.43ms (1.67ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13087053)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15138218)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 47.78ms (46.98ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339468)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 73.97ms (3.05ms CPU time)\n\nRan 5 tests for test/ComponentIsolation.t.sol:ComponentIsolationTest\n[PASS] testBootstrapRejectsWrongAssetOrStakeDestinationWithoutConsumingEligibility() (gas: 2506382)\n[PASS] testRegistryCompleteTransitionMatrixPreservesHistoryAndOriginalTerms() (gas: 14386872)\n[PASS] testResolutionRejectsForeignAssetOrUnequalArbitrationBondWithoutPartialState() (gas: 1988587)\n[PASS] testSubsidyRejectsWrongTokenOrThresholdWithoutConsumingGrant() (gas: 874651)\n[PASS] testTwoRealMarketsShareComponentsButNeverForecastsStakeOrCases() (gas: 3973039)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 146.48ms (15.68ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925874)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588359, ~: 1589052)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779311)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829286)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926196)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 227.22ms (332.96ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89064, ~: 89138)\nLogs:\n  Bound result 96006865738022423100315\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 233.14ms (89.95ms CPU time)\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517255)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763002)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690914)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416609, ~: 416372)\nLogs:\n  Bound result 1\n  Bound result 39697216372694171177\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947441)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 233.28ms (33.65ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805621)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393706)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1904982, ~: 1905023)\nLogs:\n  Bound result 975000064915284163135255\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493379)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 233.84ms (204.49ms CPU time)\n\nRan 17 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337255)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2660158)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131364)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006199)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2533153)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503452, ~: 2514625)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6721)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310283)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652334)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038424)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681305)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453967)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266857)\n[PASS] testThirdValueOverturnNeverPaysSlashToTheResolverItself() (gas: 2334033)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2492110)\n[PASS] testThirdValueOverturnWithNoEntriesStillSlashesToDisputer() (gas: 2159996)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585640)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 233.96ms (253.00ms CPU time)\n\nRan 5 tests for test/LifecycleProperties.t.sol:LifecyclePropertiesTest\n[PASS] testEveryForecastIdenticalIncludingSignedMinimumStillHasAWinner() (gas: 3083334)\n[PASS] testFuzzFiveEntrantsAllBondsRecoverAndFinalWealthIsConserved(int256[5],int256,int256,uint96,uint8,uint8) (runs: 1000, μ: 4319470, ~: 4428579)\nLogs:\n  Bound result 5000000000000000000000\n\n[PASS] testFuzzOverturnSlashRoutingAndConservation(int8[4],uint8,int8,int8,int8) (runs: 1000, μ: 2398509, ~: 2414781)\n[PASS] testOneWeiShortRewardAndBondAllowancesFailAtomically() (gas: 1328506)\n[PASS] testResolutionEventIncludesTruthAndCorrectIndexedWinner() (gas: 1261955)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 234.06ms (464.97ms CPU time)\n\nRan 1 test for test/DonationLedgerInvariant.t.sol:DonationLedgerInvariantTest\n[PASS] invariantDonationsCannotBeDoubleCreditedOrReleasedWithoutBacking() (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------------+-------------------+-------+---------+----------╮\n| Contract              | Selector          | Calls | Reverts | Discards |\n+========================================================================+\n| DonationLedgerHandler | bootstrapResolver | 2770  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | deposit           | 2696  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | replaceOpenBounty | 2751  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | subsidize         | 2741  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | sync              | 2731  | 0       | 0        |\n|-----------------------+-------------------+-------+---------+----------|\n| DonationLedgerHandler | transferDirectly  | 2695  | 0       | 0        |\n╰-----------------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 200000000000000000000\n  Bound result 199999999999999999999\n  Bound result 1\n  Bound result 1\n  Bound result 49\n  Bound result 163\n  Bound result 77\n  Bound result 1000\n  Bound result 7746\n  Bound result 77\n  Bound result 999999999999999999981\n  Bound result 1100\n  Bound result 999999999999999999956\n  Bound result 2553\n  Bound result 10789\n  Bound result 9006\n  Bound result 5000000000000000000\n  Bound result 11729\n  Bound result 1000000000000000000000\n  Bound result 933493616555517276237\n  Bound result 442\n  Bound result 999999999999999600001\n  Bound result 3246\n  Bound result 3188510922\n  Bound result 995867871798503382717\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.24s (3.24s CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.25s (7.25s CPU time)\n\nRan 1 test for test/StakeLedgerInvariant.t.sol:StakeLedgerInvariantTest\n[PASS] invariantIndependentLedgerAcrossMarketsAndActors() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------------+---------------------+-------+---------+----------╮\n| Contract           | Selector            | Calls | Reverts | Discards |\n+=======================================================================+\n| StakeLedgerHandler | deposit             | 4122  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | donate              | 4073  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | lock                | 4064  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | replayOrCrossMarket | 4123  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | settle              | 4166  | 0       | 0        |\n|--------------------+---------------------+-------+---------+----------|\n| StakeLedgerHandler | withdraw            | 4028  | 0       | 0        |\n╰--------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 25000000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 2393\n  Bound result 1\n  Bound result 3\n  Bound result 3\n  Bound result 8000000\n  Bound result 2\n  Bound result 1\n  Bound result 2\n  Bound result 3\n  Bound result 11178\n  Bound result 5823\n  Bound result 1\n  Bound result 2304248604481655103721\n  Bound result 1\n  Bound result 4453\n  Bound result 2\n  Bound result 2\n  Bound result 999999999999999990784\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 3000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 4777\n  Bound result 1\n  Bound result 255\n  Bound result 1\n  Bound result 9999\n  Bound result 1\n  Bound result 7\n  Bound result 3\n  Bound result 1\n  Bound result 3662\n  Bound result 261146084642026555008\n  Bound result 1\n  Bound result 2\n  Bound result 8000000\n  Bound result 8664\n  Bound result 37886277137558400841677\n  Bound result 1\n  Bound result 434895301792413441634\n  Bound result 1\n  Bound result 11779502142297\n  Bound result 1\n  Bound result 8052\n  Bound result 1\n  Bound result 1\n  Bound result 27947855252804726804563\n  Bound result 0\n  Bound result 2\n  Bound result 11241\n  Bound result 2\n  Bound result 3557\n  Bound result 2\n  Bound result 2\n  Bound result 13786992319657006856611\n  Bound result 123456789\n  Bound result 1\n  Bound result 2\n  Bound result 999999999999999500001\n  Bound result 4\n  Bound result 26553249422127987000497\n  Bound result 13000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 2\n  Bound result 259201\n  Bound result 1\n  Bound result 2\n  Bound result 4\n  Bound result 77\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.05s (10.04s CPU time)\n\nRan 13 test suites in 10.05s (22.21s CPU time): 81 tests passed, 0 failed, 0 skipped (81 total tests)\n","passed":true},{"durationMs":62,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":321,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/ADDED_COVERAGE.md\":45,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/ComponentIsolation.t.sol\":207,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":406,\"test/DonationLedgerInvariant.t.sol\":235,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/LifecycleProperties.t.sol\":281,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139,\"test/StakeLedgerInvariant.t.sol\":250},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e5a438dc871cc21e0dc2a34c6a6427b1b98d1bfc111f0dc8989c83f8fa9a8e14","verifiedTreeHash":"c19dee7238674dc560bc9c93669581f8ebe05acf","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3920,"exitCode":0,"name":"build","output":"Compiling 55 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.84s\nCompiler run successful!\n","passed":true},{"durationMs":6978,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/BountyRegistry.t.sol:BountyRegistryTest\n[PASS] testForeignNamespaceCannotRewriteCanonicalRecord() (gas: 866195)\n[PASS] testRejectsInvalidRecordsAndMissingIds() (gas: 130300)\n[PASS] testTransitionsAreForwardOnlyAndOriginalTermsRemainIntact() (gas: 1976800)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 8.88ms (2.19ms CPU time)\n\nRan 5 tests for test/ForecastToken.t.sol:ForecastTokenTest\n[PASS] testFixedSupplyAndMetadata() (gas: 98984)\n[PASS] testFuzzTransferConservesSupply(uint96) (runs: 256, μ: 89090, ~: 89150)\nLogs:\n  Bound result 162514264337593543871106\n\n[PASS] testNoMintBurnOwnerPauseOrUpgradeSelectors() (gas: 197378)\n[PASS] testTransferAndAllowanceAreExact() (gas: 167490)\n[PASS] testZeroAddressAndInsufficientBalanceRevert() (gas: 62104)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 8.89ms (7.84ms CPU time)\n\nRan 6 tests for test/AdversarialToken.t.sol:AdversarialTokenTest\n[PASS] testReentrantCreateIsRejectedWithoutLosingOriginalDeposit() (gas: 754671)\n[PASS] testReentrantDisputeDuringOpeningCannotEnterWindow() (gas: 400288)\n[PASS] testReentrantDonationSyncCannotDoubleCreditPool() (gas: 475528)\n[PASS] testReentrantStakeWithdrawalFailsInsideDeposit() (gas: 338049)\n[PASS] testRewardTransferFailureRollsBackFinalizationAndCanBeRetried() (gas: 2339500)\n[PASS] testShortAndFalseIncomingTransfersCannotCreateUnbackedRewards() (gas: 578521)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 19.41ms (1.94ms CPU time)\n\nRan 6 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testAllApplicationRuntimesBoundedAndWithoutEscapeOpcodes() (gas: 13076468)\n[PASS] testAtomicConstructionPreservesSupplyAndUsesCanonicalConfig() (gas: 15129869)\n[PASS] testConstructorsWorkBeforeDependenciesExist() (gas: 25300)\n[PASS] testInvalidConstructorParametersFailClearly() (gas: 4000)\n[PASS] testNoApplicationOwnerPauseOrUpgrade() (gas: 571535)\n[PASS] testPinnedPanelAddressesAreDistinctNonzeroAndHaveNoCode() (gas: 12653)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 28.77ms (28.89ms CPU time)\n\nRan 7 tests for test/ResolverStake.t.sol:ResolverStakeTest\n[PASS] testAllLocksMustFinishEvenWhenOneWindowIsAlreadyFinal() (gas: 2805653)\n[PASS] testBadDepositsAndZeroWithdrawalsRevert() (gas: 284235)\n[PASS] testConcurrentResolutionsReserveFullSlashAndCannotOvercommit() (gas: 8393931)\n[PASS] testFuzzSnapshotSlashRoundsDownAndPreservesBacking(uint96) (runs: 256, μ: 1905027, ~: 1905068)\nLogs:\n  Bound result 637514264337593543869075\n\n[PASS] testPermissionlessDepositsForSelfAndOthersAndPartialWithdrawals() (gas: 593245)\n[PASS] testTopUpDoesNotChangePreviouslyReservedSlash() (gas: 2493424)\n[PASS] testUntrustedMarketCannotLockReleaseOrSlashCanonicalStake() (gas: 1824114)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 48.73ms (51.24ms CPU time)\n\nRan 15 tests for test/DisputeWindow.t.sol:DisputeWindowTest\n[PASS] testChallengeRulesAndBondTransferRollback() (gas: 2337299)\n[PASS] testDifferentCorrectionsRequireTwoVotesForTheSameValue() (gas: 2659905)\n[PASS] testExactWindowBoundaryAndNoUnchallengedPanelVote() (gas: 2131402)\n[PASS] testFakeMarketCannotSpendResolverApprovalOrAffectCanonicalCase() (gas: 2006243)\n[PASS] testFalseFirstChallengeCannotPreventPanelFromCorrectingActualValue() (gas: 2532922)\n[PASS] testFuzzPanelOrderAlwaysRequiresTwoDistinctVotes(bool,bool,bool,uint8) (runs: 256, μ: 2503471, ~: 2514631)\n[PASS] testImmutableSignersRejectAllDuplicatesAndZeroPositions() (gas: 6689)\n[PASS] testLastSecondChallengeExtendsLockUntilPanelDecision() (gas: 2310289)\n[PASS] testOnlyPanelCanVoteOnceAndDecisionCannotBeReplayed() (gas: 2652380)\n[PASS] testPanelTimeoutReturnsBondsKeepsWinnerAndReleasesStake() (gas: 3038452)\n[PASS] testRejectedDisputePaysResolverAndDoesNotSlash() (gas: 2681300)\n[PASS] testSockDisputerCannotRecoverResolverSlash() (gas: 2453768)\n[PASS] testSuccessfulDisputeCorrectsWinnerSlashesAndPaysEqualBonds() (gas: 3266913)\n[PASS] testThirdValueOverturnPaysSlashToCorrectedWinnerNotWrongDisputer() (gas: 2491879)\n[PASS] testThreeDifferentPanelValuesTimeOutKeepingOriginalResolution() (gas: 2585668)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 53.83ms (67.68ms CPU time)\n\nRan 10 tests for test/FundingPools.t.sol:FundingPoolsTest\n[PASS] testBootstrapRejectsOverturnedEmptyCancelledAndWithdrawnStake() (gas: 5517332)\n[PASS] testBootstrapRequiresFinalizationFundingAndCreditsOnlyResolverStake() (gas: 3763034)\n[PASS] testCreatorCannotHaveTwoSubsidisedOpenBounties() (gas: 3690930)\n[PASS] testFuzzDepositsCannotBeCreditedTwice(uint96,uint96) (runs: 256, μ: 416603, ~: 416372)\nLogs:\n  Bound result 129706696399857062819504\n  Bound result 4324560441759062235\n\n[PASS] testLateSubsidyAndInsufficientPoolCannotPay() (gas: 910537)\n[PASS] testNoDonorOrAdminCanWithdrawEitherPool() (gas: 535373)\n[PASS] testPoolsStartEmptyAndAnyoneCanFundBothWays() (gas: 751488)\n[PASS] testRefundedSubsidisedBountyKeepsSlotSoCreateRefundLoopCannotFarm() (gas: 1593589)\n[PASS] testSubsidisedBountyFinalizedWithoutEntriesKeepsSlot() (gas: 1947457)\n[PASS] testSubsidyNeedsFundingAndOnlyCreatorGetsOneGrant() (gas: 1124856)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 97.08ms (26.69ms CPU time)\n\nRan 14 tests for test/ForecastBounty.t.sol:ForecastBountyTest\n[PASS] testCannotRefundResolvedBountyWithEntries() (gas: 1925890)\n[PASS] testCreateEscrowsAndIndexesImmutableTerms() (gas: 673683)\n[PASS] testCreateRejectsBadTermsAndUnfundedReward() (gas: 575186)\n[PASS] testCreatorCanRefundUnresolvedAtDeadlineAndBondsRemainWhole() (gas: 1224152)\n[PASS] testForecastPrivacyDuplicateAndDeadline() (gas: 944783)\n[PASS] testFuzzClosestSignedValue(int256,int256,int256) (runs: 256, μ: 1588381, ~: 1589087)\n[PASS] testMaximumEntriesSinglePassRemainsBounded() (gas: 115794745)\n[PASS] testPermissionlessExpiryAndDuplicateFinalizationFailSafely() (gas: 897870)\n[PASS] testResolveRequiresCreatorDeadlineStakeAndBond() (gas: 2367320)\n[PASS] testResolvedEmptyBountyRefundsWithoutTakingAnyBonds() (gas: 1779327)\n[PASS] testSilentCreatorCannotLockParticipantBondForever() (gas: 1109901)\n[PASS] testTieGoesToFirstSubmissionAndSignedExtremesWork() (gas: 2829302)\n[PASS] testUnknownAndUnfundedForecastRollback() (gas: 828487)\n[PASS] testWinnerClaimsOnlyAfterWindowAndEveryBondIsRefunded() (gas: 2926228)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 98.99ms (139.30ms CPU time)\n\nRan 1 test for test/ProtocolInvariant.t.sol:ProtocolInvariantTest\n[PASS]\nProtocolInvariantTest invariants:\n[PASS] invariantBountyLiabilitiesAndLocksMatchActualUnpaidObligations\n[PASS] invariantEveryCustodiedTokenHasMatchingLiabilityAndSupplyIsConserved\n[PASS] invariantNoGetterRevealsForecastBeforeResolution\n ProtocolInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-----------------+-----------------------+-------+---------+----------╮\n| Contract        | Selector              | Calls | Reverts | Discards |\n+======================================================================+\n| ProtocolHandler | advance               | 459   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | bootstrapResolver     | 457   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | challenge             | 428   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | claim                 | 476   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | create                | 446   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | depositStake          | 450   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | expire                | 410   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | finalize              | 503   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | forecast              | 447   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | fundPools             | 461   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | refund                | 430   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | resolve               | 454   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | subsidize             | 444   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | vote                  | 435   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | voteValue             | 443   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawBond          | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawDisputeCredit | 485   | 0       | 0        |\n|-----------------+-----------------------+-------+---------+----------|\n| ProtocolHandler | withdrawStake         | 479   | 0       | 0        |\n╰-----------------+-----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.89s (6.89s CPU time)\n\nRan 9 test suites in 6.89s (7.26s CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":49,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BountyRegistry.append(address,bytes32,uint256,uint256)\",\"BountyRegistry.transition(uint256,uint8,int256)\",\"BountySubsidy.deposit()\",\"BountySubsidy.deposit(uint256)\",\"BountySubsidy.subsidize(uint256)\",\"DisputeWindow.dispute(address,uint256,int256)\",\"DisputeWindow.finalize(address,uint256)\",\"DisputeWindow.open(uint256,address,int256)\",\"DisputeWindow.vote(address,uint256,bool)\",\"DisputeWindow.voteValue(address,uint256,int256)\",\"DisputeWindow.withdrawPayout()\",\"ForecastBounty.claim(uint256)\",\"ForecastBounty.createBounty(string,uint256,string,uint256)\",\"ForecastBounty.expire(uint256)\",\"ForecastBounty.finalize(uint256)\",\"ForecastBounty.forecast(uint256,int256)\",\"ForecastBounty.onDisputed(uint256)\",\"ForecastBounty.refundReward(uint256)\",\"ForecastBounty.resolve(uint256,int256)\",\"ForecastBounty.withdrawBond(uint256)\",\"ForecastToken.approve(address,uint256)\",\"ForecastToken.transfer(address,uint256)\",\"ForecastToken.transferFrom(address,address,uint256)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"ResolverBootstrap.bootstrap(uint256)\",\"ResolverBootstrap.deposit()\",\"ResolverBootstrap.deposit(uint256)\",\"ResolverStake.deposit(address,uint256)\",\"ResolverStake.depositFor(address,address,uint256)\",\"ResolverStake.lock(uint256,address)\",\"ResolverStake.settle(uint256,bool,address)\",\"ResolverStake.withdraw(address,uint256)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":17,\"LICENSE\":21,\"README.md\":141,\"SECURITY.md\":52,\"VALIDATION.md\":36,\"foundry.toml\":21,\"launch.json\":69,\"remappings.txt\":2,\"script/DeployForecast.s.sol\":46,\"src/BountyRegistry.sol\":80,\"src/BountySubsidy.sol\":56,\"src/DeploymentConfig.sol\":24,\"src/DisputeWindow.sol\":193,\"src/ForecastBounty.sol\":315,\"src/ForecastToken.sol\":11,\"src/LaunchToken.sol\":7,\"src/ResolverBootstrap.sol\":51,\"src/ResolverStake.sol\":99,\"src/lib/DonationPool.sol\":41,\"src/lib/TokenPool.sol\":30,\"test/AdversarialToken.t.sol\":172,\"test/BountyRegistry.t.sol\":56,\"test/Deployment.t.sol\":148,\"test/DisputeWindow.t.sol\":355,\"test/ForecastBounty.t.sol\":327,\"test/ForecastToken.t.sol\":69,\"test/FundingPools.t.sol\":235,\"test/ProtocolInvariant.t.sol\":329,\"test/ProtocolTestBase.sol\":127,\"test/ResolverStake.t.sol\":139},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"fe0e5e77a1556deaba4d10567a599a5192a1cdcfb2558230a2e723bab89e8923","verifiedTreeHash":"d32afaf8bfa2a6076acba3af48d7adbbe2fef63b","verifierVersion":"0.1.0+c3fba9eb"}]}