{"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":"84e3719a-76be-40ed-92fb-c792d6490dba","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"e6ee6e300f17b09e9089435a7694c37e90a12b6a0621bd6ea0c78915a029aa06","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":"1f774cba76b816a6bb7f837dfd9efe0fce8bb556b61deb06d5fbec9818c2efe8","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":"7397c5f4c22866115ecf95f81320266c9ca8b57fa2f250a15948d8cb3e5e6d45","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":"41ddf1e50c392c58713919eeb8e59529848cf584bb17f524111477ecd3c8b906","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":"27bc564de6631ef060626f7a6a88b69dd1841aa8d7fa3b4dfb4d06fea4dc55a9","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":"37b9049d55d0aeef38c846534a1532438fc0eba9ba1449343d2ca025033626b2","dependsOn":[],"execution":{"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":"f37e44cdcb6e67bb7e90c88b83d1d8700d1575dd996724d63c0e14a67f8d7cba","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"84fb5a093d94004bfdeaa3e9f8043d1e3f060725e34e8634651cd3f0efcc5d33","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":"Build \"Keel Spin\": a Foundry project with two contracts, KeelSpinVault and KeelFwaRouter. Ethereum mainnet, contracts only. Users pool small ETH bets into rounds; a funded round buys one spin on the Fake World Assets V2 (FWAV2) main pool. For each won NFT the vault keeps it or takes the ETH bid, by its real sellable value. Kept NFTs go to teamWallet, which sells them and returns proceeds to the round's bettors.\n\nADDRESSES (constructor args, owner-settable): FWAV2 0x958C41181182e76F221331b2755b77D9e1426A98; CollectionFloorOracle 0xaA4D9009a4664604b57644bF13e447E9036727DC; IMD Intake v2 0xa43e6f75ee006411f79ac1c84120606c2330de82; IMD 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7; IMD oracle signer; teamWallet (default $owner). Use their verified sources and github.com/adamlizek/fwa-examples (FWAV2BuilderBase).\n\nDEPLOY: KeelSpinVault first, then KeelFwaRouter with $contract:KeelSpinVault; static constructor args only; the owner calls vault.setRouter after.\n\nROUNDS: bet() min 0.001 ETH (owner-set), max 500 bettors. liveTarget = quoteAcquisitionPrice() total (pool fee + VRF fee at tx.gasprice) * (10000 + bufferBps, default 500) / 10000 + spinReward. Bets fill the round to liveTarget; excess goes to the next round. Never lock a round at a stale price. Withdraw allowed while Open.\n\nSPIN: anyone calls spin(roundId). Read the live quote in the same call; require balance >= quote + spinReward and tx.gasprice <= maxSpinGasPrice, else revert. Pay the caller spinReward (default 0.002 ETH, max 2% of quote). The router calls FWAV2.acquire(purchaser = vault, 1, maxAcquisitionFee = quoted pool fee, minWeightedValue, maxNegativeSlippageBps); only the vault may call the router; it earns builder revenue, which the owner claims to teamWallet. Revert during blackouts 11:45-12:00 and 23:45-00:00 UTC. Unspent buffer and overpayment refunds stay with the round's bettors pro rata. Refunded request: claim the refund credit, round back to Open, VRF fee shared pro rata.\n\nDECISION: ethBid = backing * settlementDiscountBps() / 10000, read live. requestDecision(roundId):\n1. Read floor and its timestamp from CollectionFloorOracle; missing or older than maxFloorAge (24h) = unavailable. If floor * (10000 - haircutBps 1500) / 10000 >= ethBid * 3, keep at once.\n2. Else pay 0.5 IMD via Intake.requestWithFailure(\"oracle.request@oracle-1\", body, onOracleResult, onFailure). Body built onchain: v 1, chainId 1, answerType uint256, evidence panel, window {hours:1}, panelSize 5, quorum 4, toleranceBps 500, validForSeconds 3600, guards min 0 max 1e22. Question: \"What is the highest live offer, in wei, that the owner of <collection> token <tokenId> could accept right now, after marketplace fees and creator royalties?\" Owner-settable definitions, each under 512 chars: offers = best token, collection or matching trait offer, WETH counts, ignore offers expiring within 2h; sources = OpenSea, Blur, and for CryptoPunks or wrappers the native market 0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb; net = what the seller actually receives after that marketplace's fee and enforced royalty; missing = never invent offers, answer 0 if none.\nonOracleResult only stores the value: only from Intake, only pending requestIds, verify the EIP-712 signature, agreed >= quorum, not expired. executeDecision (anyone): keep if netOffer * 10000 > ethBid * (10000 + marginBps 500), else take the ETH bid. Add imdBalance(), needsImdTopUp() (below 2 IMD) and a LowImdBalance event.\n\nDEADLINE: after FWA settlementWindow the depositor can reclaim the NFT for just the bid. decisionDeadline = allocatedAt + settlementWindow - safetyMargin (2h). If no valid value by deadline - fallbackLead (1h), or the oracle failed, settleFallback (anyone) keeps if a fresh floor * (10000 - haircut) / 10000 > ethBid * (10000 + margin) / 10000, else takes ETH; no fresh floor = defaultKeep (true). Never take the FWA token cashout, never relist. If the depositor or a finalizer resolves first, settle with what arrived.\n\nKEEP NFT: onERC721Received; forward the NFT to teamWallet (forwardNFT retries if that fails); support recoverStuckNFT. Round -> AwaitingSale; emit collection, tokenId, ethBid, netOffer and decision path. teamWallet or owner calls depositSaleProceeds(roundId, saleTxHash) with ETH, once; round -> Settled; emit SaleProceeds. ETH path: the bid arrives, round -> Settled.\n\nPAYOUTS: pull claims pro rata to stake; feeBps (default 0, max 300) to teamWallet.\n\nSAFETY: Ownable($owner) with setters for all params; pause new bets only; withdrawIMD; the owner can never move bettor ETH; ReentrancyGuard and checks-effects-interactions; events for every state change.\n\nTESTS: carry-over; price rise keeps round Open; gas cap; spinReward paid; buffer returned; blackout; refund path; obvious keep (30 ETH floor vs 1 ETH backing); stale floor skips pre-check; oracle above and below margin; oracle failure fallback; deadline; depositor resolves first; NFT to teamWallet; proceeds pro rata; bad attestation and non-Intake callback rejected; fee cap. Invariant: claimable plus Open balances never exceed vault ETH. Fuzz up to 500 bettors.","parentJobId":null,"planHash":"1e90d8ccc6ebe5e3423f4f433ee82249c8478793bd44d5dcec9d57d5c43ee2c8","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"84e3719a-76be-40ed-92fb-c792d6490dba","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2164-build-keel-spin-foundry"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52160","feedbackHash":"be1b35dd8f3567ca56a5b2194ea727d5b4b8f94851799e2152a0002a71b5c58b","nodeKey":"audit_economics","submissionHash":"e6ee6e300f17b09e9089435a7694c37e90a12b6a0621bd6ea0c78915a029aa06","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53924","feedbackHash":"8f452687a56aff884f6194187ff17d2c7cda6fcf7b4cfc5ca2ae38c7c7f6e177","nodeKey":"audit_flow","submissionHash":"1f774cba76b816a6bb7f837dfd9efe0fce8bb556b61deb06d5fbec9818c2efe8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52231","feedbackHash":"87e20a39cfb99c94c2edeccbcf4f05e35caf195abc97aae3b7676649986548fb","nodeKey":"audit_judge","submissionHash":"7311eaf1b558a03361123d3421f4f4bf8a5d35b6988d01f851e09a262362278a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51512","feedbackHash":"bc3217c6b13233f54f38c774760ccb536fbb55f1ddd5c2a694b1dc7708c88efc","nodeKey":"audit_judge","submissionHash":"7397c5f4c22866115ecf95f81320266c9ca8b57fa2f250a15948d8cb3e5e6d45","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52176","feedbackHash":"939756f8901223c537e5974ae89927d8309115636022e97cc2b746a634055821","nodeKey":"audit_judge","submissionHash":"6692d943dac7b262555d203ea84b648503ef1755ddbf3d22e8d4ddd99b5c6069","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52129","feedbackHash":"1c3e904ba6ca0c7059aa85c3f9fb0ce3337ae3367179043b7940f207b104e7d5","nodeKey":"audit_math","submissionHash":"41ddf1e50c392c58713919eeb8e59529848cf584bb17f524111477ecd3c8b906","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51447","feedbackHash":"3ebba041011ef8d97655ba8a1a39bfdfcc7d56fef77000eb9adb92a32f156187","nodeKey":"audit_permissions","submissionHash":"27bc564de6631ef060626f7a6a88b69dd1841aa8d7fa3b4dfb4d06fea4dc55a9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52548","feedbackHash":"8d6518d6ede82d11184891b7e00c9a6703e31409979ca24d7261b45ab01d6b3b","nodeKey":"build_contract_project","submissionHash":"6e61cdf13d63c5e194966570d7bba1f48e7c39a19cf9bde8bd5609efd3579cbc","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52196","feedbackHash":"4bcf1e85867e6a538ad24b991d7483faf9d25ed988c3447f1e7c19759b8972dc","nodeKey":"build_contract_project","submissionHash":"3af45e5fd441d8d7426b910232729fdbc91b8d94b3470ec453ec29f95a3d43d5","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52178","feedbackHash":"39b99cd8e14554c2c11d12ff96d1d8f43ad48161782aa382cda1941c6034407b","nodeKey":"build_contract_project","submissionHash":"37b9049d55d0aeef38c846534a1532438fc0eba9ba1449343d2ca025033626b2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51537","feedbackHash":"af234c3375c5dad9d606f406623ddc56b8f8af4baaf02ce0c0faa53571d57a0e","nodeKey":"manifest","submissionHash":"145fa241ba5d48056c0d0e72b781649cae9b83a89ed5c1688846be32c2c2b581","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51072","feedbackHash":"4ef2911295342ad0ae5122b6705901eaff8d8b66da7456ffdc45f57117e37637","nodeKey":"manifest","submissionHash":"9b9eddd6d13327568fbbbedf557a6233581f5f1e26469ca99119335dce29fa70","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52261","feedbackHash":"1c030668ec512224a4f15c3e08f495ff5685a5dc4fe1eb0ec009e606573d594d","nodeKey":"manifest","submissionHash":"f37e44cdcb6e67bb7e90c88b83d1d8700d1575dd996724d63c0e14a67f8d7cba","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51880","feedbackHash":"d44d5c63620cbd829ffa8a799d2c99a870f58d8791d032ce4bc6140218de0c18","nodeKey":"write_foundry_tests","submissionHash":"bbef090e124f989527190fb3b8095d1f266e3e4037e20813364600a69da5365a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52375","feedbackHash":"fbe239b004c4d54b7149dab2fffb102db6269cbec2c111272ff13f96d9752574","nodeKey":"write_foundry_tests","submissionHash":"5992161059ab592bd998d2fd9b700944b2f37a25c5a76991ede6a8be320d8a4b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51234","feedbackHash":"e67f89c35255d56bed55fb4c1990d2a0f9df33b0d04fcfc36ac3768cb412ad67","nodeKey":"write_foundry_tests","submissionHash":"84fb5a093d94004bfdeaa3e9f8043d1e3f060725e34e8634651cd3f0efcc5d33","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9e8d85176f801cb4048f0c6291d5c004977e8de837fa27f66d441ab1b0d05d24","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"6ebc1e8d7babfe0c2b22c8c0edbca0ec3dcea34b564bc3a5b48222531d7c7638","device":"1da032f42d59f73b","findings":[],"hash":"145fa241ba5d48056c0d0e72b781649cae9b83a89ed5c1688846be32c2c2b581","nodeId":"ff02171a-f18a-4f87-85b7-f763c4899e07","outcome":"completed","summary":"I checked `launch.json` against the current source and left it unchanged. It still matches the accepted implementation, so nothing in this revision needed a change.\n\n- **KeelSpinVault**: takes `owner_, fwa_, floorOracle_, intake_, imd_, signer_, teamWallet_`. The manifest gives it `$owner`, the FWAV2, CollectionFloorOracle, Intake v2 and IMD addresses from the brief, the oracle signer `0x5598aa9146215bc13eb26f2c692ad1461fd32982`, and `$owner` as teamWallet. I checked the signer against the live IMD oracle API, which reports it as both the attester and the signer.\n- **KeelFwaRouter**: takes `owner_, vault_, pool_`. The manifest gives it `$owner`, `$contract:KeelSpinVault` and the FWAV2 address, so it comes after the vault as the brief requires.\n- **Format**: the file has only `kind`, `contracts` and `notes`, and the notes are 648 characters. They describe the `vault.setRouter` step after deployment and the owner-set FWA transfer helper.\n\n**Checks:**\n- `forge build` passes.\n- `forge test` with the verifier's fuzz seed: 102 of 102 tests pass.\n- I deployed both contracts with exactly the manifest's arguments on an empty chain (chain id 1) in a scratch test. Both constructors succeed without calling any outside contract, both owners and the team wallet are the launch owner, and both contracts are under the 24,576-byte size limit.\n\nThe two protected deployment checks you supplied weren't run here, because they need the launch's factory settings and a helper file that this machine doesn't have.","treeHash":"fa95bb6aadf82c87622ce68a84471a7cea83b3b5","usage":{"cachedInputTokens":278942,"inputTokens":16,"model":"claude-opus-5-5","outputTokens":3800,"runtime":"claude","turns":8,"wallClockMs":324986}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7ac9f7a7687c25e","findings":[{"citation":"resolved","description":"requestDecision is permissionless. Its only timing gate before paying the Intake is `block.timestamp >= fallbackAt(id)` (line 404); any call one block earlier proceeds to `intake.requestWithFailure` (line 416) and spends oraclePrice (0.5 IMD). The callback onOracleResult (line 442) refuses every delivery at `block.timestamp >= fallbackAt(id)`, and a delivery is a later transaction (the reference says answers come minutes later). So a request made inside the delivery latency of fallbackAt, and with certainty one made in the last block before it, is paid for and can never be stored: the round then settles through _fallback exactly as if nothing had been asked. Anyone can trigger this every round at the cost of gas (an attacker calls requestDecision at fallbackAt-1 for each allocation), and honest keepers hit it naturally whenever they ask late. On mainnet the live FWAV2 settlementWindow() is 3600 s, so for the oracle to be used at all the owner must set safetyMargin+fallbackLead below one hour; the usable window is then minutes from allocatedAt (not from the request), which makes this waste the common case rather than an edge. Fix: before paying, require `fallbackAt(id) >= block.timestamp + minOracleLead` (an owner-set lead covering panel latency, e.g. 10-15 minutes), otherwise take the fallback path (or return) without spending IMD; optionally also require the lead in settleFallback/onOracleResult consistently.","line":404,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\nimport {MockFWA, MockNFT, MockIMD, MockFloor, MockIntake, RejectNFT} from \"test/mocks/Protocols.sol\";\n\n/// Finding: requestDecision pays 0.5 IMD for a question whose answer can never be stored,\n/// because it only checks `block.timestamp < fallbackAt(id)` and the callback requires the\n/// same strict inequality at delivery time (at least one block later).\ncontract ImdLeadTest is Test {\n    KeelSpinVault vault;\n    KeelFwaRouter router;\n    MockFWA pool;\n    MockNFT nft;\n    MockIMD imd;\n    MockFloor floor;\n    MockIntake intake;\n    address alice = address(0xA11CE);\n    address team = address(0x7EA7);\n    address depositor = address(0xD3F0);\n    address keeper = address(0xB07);\n    uint256 constant SIGNER_KEY = 0xA77E57;\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        pool = new MockFWA();\n        nft = new MockNFT();\n        imd = new MockIMD();\n        floor = new MockFloor();\n        intake = new MockIntake();\n        vault = new KeelSpinVault(\n            address(this), address(pool), address(floor), address(intake), address(imd), vm.addr(SIGNER_KEY), team\n        );\n        router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        vm.deal(address(pool), 100 ether);\n        vm.deal(alice, 100 ether);\n        vm.deal(keeper, 1 ether);\n        imd.mint(address(vault), 100 ether);\n    }\n\n    function _ready() internal returns (uint256 id) {\n        id = vault.currentRoundId();\n        vm.prank(alice);\n        vault.bet{value: vault.liveTarget()}();\n        vm.prank(keeper);\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether, depositor);\n        vault.syncRound(id);\n    }\n\n    function _attestation(uint256 answer) internal view returns (OracleAttestation.Attestation memory a) {\n        a.requestId = keccak256(abi.encode(\"oracle UUID\", intake.nonce()));\n        a.chainId = 1;\n        a.questionHash = keccak256(\"q\");\n        a.answerType = 3;\n        a.answer = abi.encode(answer);\n        a.figure = answer;\n        a.fromBlock = 100;\n        a.toBlock = 200;\n        a.blockHash = bytes32(uint256(7));\n        a.panelJobId = keccak256(\"panel\");\n        a.panelSize = 5;\n        a.quorum = 4;\n        a.agreed = 4;\n        a.issuedAt = uint64(block.timestamp);\n        a.expiresAt = uint64(block.timestamp + 1 hours);\n    }\n\n    function _sign(OracleAttestation.Attestation memory a) internal view returns (bytes memory) {\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_KEY, vault.attestationDigest(a));\n        return abi.encodePacked(r, s, v);\n    }\n\n    function testRequestOneSecondBeforeFallbackBurnsImdForNothing() public {\n        uint256 id = _ready();\n        uint256 imdBefore = vault.imdBalance();\n        // Anyone may call this; 0xBAD does so one second before the missing-value cutoff.\n        vm.warp(vault.fallbackAt(id) - 1);\n        vm.prank(address(0xBAD));\n        vault.requestDecision(id);\n        bytes32 requestId = vault.rounds(id).oracleRequestId;\n        uint256 spent = imdBefore - vault.imdBalance();\n\n        // Fastest conceivable delivery: the next block, 12 seconds later. The vault refuses it.\n        vm.warp(block.timestamp + 12);\n        if (requestId != 0) {\n            OracleAttestation.Attestation memory a = _attestation(2 ether);\n            assertFalse(intake.result(requestId, a, _sign(a)), \"a correct answer is rejected as stale\");\n            assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.OraclePending));\n        }\n        // The round still resolves through the floor fallback, exactly as if nothing had been asked.\n        if (vault.activeRoundId() == id) vault.settleFallback(id);\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.AwaitingSale));\n\n        // Expected: nothing is paid for a question that cannot be answered in time. Actual: 0.5 IMD gone.\n        assertEq(spent, 0, \"IMD spent on an unanswerable request\");\n    }\n}","reproduction":"State: round allocated (syncRound done), floor stale so the pre-check does not keep, 100 IMD in the vault, mock pool with the default 24 h window (same logic for any window). 1) vm.warp(vault.fallbackAt(id) - 1); any address calls vault.requestDecision(id). Actual: state OraclePending, imdBalance() drops by 0.5e18, intake request created. 2) vm.warp(+12 s) (next block); Intake delivers a correctly signed attestation (panel 5/4/4, uint256 2 ether) -> onOracleResult reverts StaleAnswer (intake.result returns false). 3) settleFallback(id) resolves the round via the floor fallback. Expected: no IMD is spent for a question that cannot be answered before the cutoff (requestDecision should fall back or return). Actual: 0.5 IMD lost per round, repeatable by anyone. Test: test/scratch/ImdLead.t.sol fails on this code with 'IMD spent on an unanswerable request: 500000000000000000 != 0'.","severity":"medium","snippet":"        if (r.oracleFailed || block.timestamp >= fallbackAt(id)) {","title":"requestDecision pays 0.5 IMD for a question whose answer can never be accepted (no minimum lead before fallbackAt)"},{"citation":"resolved","description":"After a keep decision the round is AwaitingSale and its remaining cash (unspent buffer plus any overpayment refund) can only be released by depositSaleProceeds, which requires r.nftForwarded (line 613). _forward (line 587-598) only sets nftForwarded when safeTransferFrom to teamWallet succeeds. If the collection permanently refuses transfers out of the vault (paused/blacklisted/broken ERC-721, or an NFT permanently stuck in FWA whose recoverStuckNFT keeps reverting), nothing can ever move the round on: claim/withdraw revert (not Settled/Open), settleFallback/executeDecision/requestDecision revert WrongState (_sync needs activeRoundId == id, which was cleared), depositSaleProceeds reverts WrongState, and the owner has no setter or escape that settles the round or releases the buffer. The locked amount is small per round (about bufferBps of the quote plus refunds) but it is bettor ETH frozen permanently, and the active-round slot is already freed so the vault otherwise keeps operating. Suggested minimal fix preserving the design: let depositSaleProceeds (team/owner) settle an AwaitingSale round whose forward keeps failing after a grace period, or let the owner mark the NFT as unrecoverable so the round settles with the cash it holds.","line":613,"path":"src/KeelSpinVault.sol","reproduction":"1) teamWallet is a contract that rejects ERC-721s (RejectNFT) or simply: round allocated, floor.set(30 ether, now), requestDecision(id) -> ObviousFloor keep; the NFT is in the vault, state AwaitingSale, nftForwarded false (forward attempt failed) and rounds(id).cash == buffer > 0. 2) The collection halts transfers: nft.setRejectTransfers(true). 3) setTeamWallet(EOA); forwardNFT(id) -> emits NFTForwardPending, nftForwarded stays false. 4) depositSaleProceeds{value:1 ether}(id, hash) reverts WrongState; alice.claim(id) and alice.withdraw(id) revert WrongState; settleFallback(id) reverts WrongState. Vault balance == buffer, unreachable by anyone forever. Expected: an owner/team path that settles the round with its cash when the NFT is provably unmovable. Confirmed by test/scratch/StuckBuffer.t.sol (passes, i.e. demonstrates the lock).","severity":"low","snippet":"        if (r.state != State.AwaitingSale || !r.nftForwarded) revert WrongState();","title":"A kept NFT that can never be forwarded strands the round in AwaitingSale with the bettors' buffer locked and no escape"},{"citation":"resolved","description":"decisionDeadline (line 354-359) saturates to allocatedAt when settlementWindow() <= safetyMargin, and fallbackAt (line 361-365) then equals allocatedAt. The supplied FWA reference states settlementWindow() and finalizeWindow() are both 3600 s on mainnet. With the brief's defaults (2 h margin, 1 h lead) every allocation therefore has fallbackAt == allocatedAt: requestDecision takes the obvious-floor branch or immediately _fallback (line 404-406), the Intake is never asked, onOracleResult would reject any answer (line 442), and executeDecision always reverts StaleAnswer (line 480). The brief's DECISION step 2 (0.5 IMD panel valuation, margin comparison) is dead code until the owner lowers both settings, and the README (line 97) documents this as intentional saturation. It is reported because the constructor ships a configuration under which a required feature cannot run, the test suite only exercises the oracle flow against a 24 h mock window, and any workable reconfiguration (margin+lead < 1 h) leaves only minutes for a panel answer, which feeds finding 1. Suggested fix: pick defaults derived from the live window (e.g. safetyMargin 15 min, fallbackLead 15 min) or make the constructor/setters validate `safetyMargin + fallbackLead < settlementWindow()` on first use, and add a test against a 1 h window that reaches OraclePending.","line":94,"path":"src/KeelSpinVault.sol","reproduction":"pool.setWindow(1 hours) (the live mainnet value); fund, spin, allocate, syncRound(id). Observe decisionDeadline(id) == fallbackAt(id) == allocatedAt. floor.set(30 ether, now - 25 hours) (stale, so the brief says the oracle must be asked). requestDecision(id): intake.nonce() stays 0 (no request), round resolves in the same call with path FallbackDefault (defaultKeep). executeDecision(id) can never succeed. Expected per brief: OraclePending with 0.5 IMD paid, decision by netOffer vs ethBid*(1+margin). Shown by test/scratch/Review.t.sol::testLiveWindowMakesOraclePathUnreachable.","severity":"low","snippet":"    uint256 public safetyMargin = 2 hours;\n    uint256 public fallbackLead = 1 hours;","title":"Default safetyMargin (2 h) and fallbackLead (1 h) exceed the live FWAV2 settlementWindow (1 h): the paid-oracle decision path is unreachable at deployment"},{"citation":"resolved","description":"requestDecision, executeDecision and settleFallback each start with _sync(id) (lines 390, 473, 485), and _sync reverts WrongState when activeRoundId != id (line 321). _reconcileOutcome sets activeRoundId = 0 before the round becomes AwaitingSale or Settled (line 534), so the subsequent `if (r.state == State.AwaitingSale) { _forward(id); return; }` and `Settled || Open -> return` branches (lines 392-396, 475-479, 487-491) can never execute: once a round is resolved, those three entry points revert instead of retrying the forward or returning quietly, contrary to README line 101 which says they recognise the terminal listing. forwardNFT and syncRound-before-resolution still work, so there is no fund impact; this is dead code that misleads keepers (a retry of the NFT forward via requestDecision/executeDecision reverts) and should either be removed or the _sync guard relaxed for already-resolved rounds.","line":392,"path":"src/KeelSpinVault.sol","reproduction":"Allocated round, floor.set(30 ether, now), requestDecision(id) -> AwaitingSale, activeRoundId == 0. Then requestDecision(id), executeDecision(id) and settleFallback(id) all revert WrongState (expected by the code's own branches: forward retry / silent return). Shown by test/scratch/Review.t.sol::testDecisionEntryPointsRevertOnceRoundResolved.","severity":"info","snippet":"        if (r.state == State.AwaitingSale) {\n            _forward(id);\n            return;\n        }\n        if (r.state == State.Settled || r.state == State.Open) return;","title":"Terminal-state branches in requestDecision/executeDecision/settleFallback are unreachable: _sync reverts once activeRoundId is cleared"}],"hash":"1f774cba76b816a6bb7f837dfd9efe0fce8bb556b61deb06d5fbec9818c2efe8","nodeId":"a3a507cf-7604-4cb7-8771-52dd578163b9","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds four findings plus a 47-row coverage record. No source, test or configuration file was changed; scratch tests live only under `test/scratch/`, which is git-ignored.\n\n**What I found**\n\n- **Medium: IMD burned on unanswerable oracle requests.** `requestDecision` pays 0.5 IMD whenever the call lands before `fallbackAt`, but `onOracleResult` rejects any delivery at or after that same instant. A call one block before the cutoff spends the IMD for an answer that can never be stored. Anyone can trigger this each round, and honest keepers hit it naturally on mainnet where the usable window is minutes. The proof test `test/scratch/ImdLead.t.sol` fails on the current code.\n- **Low: unmovable kept NFT locks the bettors' buffer.** Once a round is AwaitingSale, releasing its cash needs `nftForwarded`. If the collection permanently refuses transfers, every entry point reverts and no owner or team escape exists. Confirmed with a scratch test.\n- **Low: default timing parameters make the paid-oracle path unreachable on mainnet.** The live FWAV2 settlement window is one hour, below the 2 h safety margin, so deadlines saturate to `allocatedAt` and the Intake is never asked. The README documents this as intentional, so I rated it low, but the brief's DECISION step is inert at deployment and the test suite only exercises it against a 24 h mock window.\n- **Info: dead terminal-state branches.** `requestDecision`, `executeDecision` and `settleFallback` can never reach their AwaitingSale or Settled handling because `_sync` reverts once `activeRoundId` is cleared.\n\n**Coverage**\n\nAll 45 listed entry points have a row, plus two invariant rows. I traced the full spin, refund, allocation, oracle callback, fallback, keep, ETH-bid, sale-proceeds and claim flows, the router's purchase and builder-reward paths, and the `OracleBody` encoder. The existing 91 tests pass. I could not verify the real FWATokenTransfer helper or Permit2 witness semantics beyond the project's mocks, so the router's reward-claim ABI remains an owner-configured assumption rather than a finding.","treeHash":null,"usage":{"cachedInputTokens":2143528,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":51725,"runtime":"claude","turns":36,"wallClockMs":829895}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e0f64c10e30e33e2","findings":[{"citation":"resolved","description":"Access-control/asymmetry guard gap. The brief makes the FWAV2 address owner-settable. setFwa (L704) and setRouter (L694) require _noBets(), and KeelFwaRouter.setPool (src/KeelFwaRouter.sol:57) requires IKeelVault(vault).totalSharesOutstanding() == 0. totalSharesOutstanding is only decreased by _burn, i.e. when a bettor withdraws or claims. Shares in a Settled round, which no longer touches fwa or the router, still count. One bettor who never calls claim(), or a griefer who bets minBet (0.001 ETH) and never withdraws or claims, makes the owner's FWA/router setters revert forever. The guard is also stricter than needed: the theft it prevents (pointing fwa at a malicious pool while Open cash or an active round exists) only concerns Open rounds and the active round. If FWA migrates its pool or the router has to be replaced, the vault keeps spinning against the old addresses and the owner has no way to change them. guard_gap: setIntake/setIMD/setOracleSigner use _noActive (active round only); setFwa/setRouter use the broader _noBets. Fix: track the shares (or cash) held by Open rounds plus the active round, and check only those. Settled rounds never read fwa or router. A stake parked in the Open round is still a griefing vector, so pair the setters with pausing bets and a delay rather than an outright zero-stake requirement.","line":824,"path":"src/KeelSpinVault.sol","reproduction":"Using the project's KeelBase fixture: id=_ready() (alice funds round 1, keeper spins, the pool allocates a 1 ETH backing listing). floor.set(0.1 ether, now); warp to fallbackAt(id); settleFallback(id), so the ETH path runs and round 1 is Settled. Now activeRoundId()==0 and the current round has totalShares==0. Alice has not claimed. The owner calls vault.setFwa(newPool): it reverts OutstandingBets. router.setPool(newPool) also reverts OutstandingBets. Expected: both succeed, because no Open or active round holds bettor ETH. Actual: both revert, and they stay blocked until every past bettor claims, which a 0.001 ETH griefer can refuse to do forever. Verified with a scratch forge test (PASS on expectRevert for both calls).","severity":"medium","snippet":"    function _noBets() internal view {\n        if (totalSharesOutstanding != 0 || activeRoundId != 0) revert OutstandingBets();\n    }","title":"_noBets counts unclaimed Settled shares, so any bettor who never claims permanently blocks setFwa, setRouter and router.setPool"},{"citation":"resolved","description":"Trust gap (access x asymmetry). setIntake, setIMD, setOracleSigner and setOraclePayment all call _noActive(), so the oracle inputs to a decision cannot change while a round is allocated. The other two decision inputs are unguarded: setFloorOracle (L711) and setDecisionParameters (L774: haircutBps, marginBps, maxFloorAge, safetyMargin, fallbackLead, defaultKeep). requestDecision, settleFallback and _resolve read them live. Once a round is Allocated, the owner can change them to choose its outcome. A fake floor or haircut=0 forces the ObviousFloor keep path, and a 0 floor age with defaultKeep=true forces FallbackDefault. Either way the NFT is sent to teamWallet (default $owner) instead of the ETH bid flowing to the bettors' round. After that the bettors' value depends on teamWallet returning sale proceeds, and depositSaleProceeds accepts any nonzero amount. This undercuts the brief's 'the owner can never move bettor ETH' within the owner's own setter powers. It is a privileged-actor finding: the setters are meant to exist, but the change should not apply to a round that is already in flight. Fix: apply _noActive() to setFloorOracle and setDecisionParameters, or snapshot haircut/margin/floorAge/defaultKeep/safety/lead and the floor oracle into Round at allocation, the way fee/feeRecipient are snapshotted at round creation.","line":711,"path":"src/KeelSpinVault.sol","reproduction":"KeelBase fixture: id=_ready(), giving backing 1 ETH and ethBid 0.9 ETH. floor.set(0.5 ether, now), a fresh floor below the bid, so the expected outcome is the ETH bid (fallback: 0.425 < 0.945). The owner deploys a MockFloor returning 100 ether at the current timestamp and calls vault.setFloorOracle(fake). This succeeds, while setOracleSigner(any) in the same state reverts SpinInProgress. Anyone then calls requestDecision(id): the round goes to AwaitingSale with path ObviousFloor, and nft.ownerOf(1)==teamWallet. Expected: the setter reverts while activeRoundId!=0 (or the round uses parameters snapshotted at allocation), and the round takes the 0.9 ETH bid. Actual: the NFT is kept and forwarded to teamWallet. Verified with a scratch forge test (PASS).","severity":"medium","snippet":"    function setFloorOracle(address to) external onlyOwner {\n        _checkAddress(to);\n        floorOracle = ICollectionFloorOracle(to);\n        emit AddressSet(\"floorOracle\", to);\n    }","title":"setFloorOracle and setDecisionParameters lack the _noActive guard that the sibling decision setters have, so the owner can flip an in-flight round from the ETH bid to keep (NFT to teamWallet)"}],"hash":"27bc564de6631ef060626f7a6a88b69dd1841aa8d7fa3b4dfb4d06fea4dc55a9","nodeId":"5bdc52a2-378d-4d14-b83b-ccdcdc324de2","outcome":"completed","summary":"I found two medium-severity defects in the permissions area and nothing critical or high. Both reproduced in a scratch Foundry test, which I've since deleted. The findings and a coverage row for all 45 entry points (plus one invariant row) are in `.imd-findings.json`. Neither finding carries a `proof` field: those are only required for critical and high, and my scratch test used the project's own test setup and mocks, which a proof isn't allowed to import.\n\n**1. Unclaimed old winnings permanently lock the owner out of changing the FWAV2 pool or router** (`src/KeelSpinVault.sol:824`)\n- **Cause:** `setFwa`, `setRouter` and `KeelFwaRouter.setPool` only work when `totalSharesOutstanding == 0`. That count still includes shares in rounds that are already Settled and no longer use the pool or router.\n- **Repro:** a round settles on the ETH path, the active round is cleared, and the new current round is empty. Alice hasn't claimed yet, so all three setters revert with `OutstandingBets`.\n- **Impact:** anyone who bets 0.001 ETH and never claims or withdraws can keep the owner out forever. The brief requires the FWAV2 address to be owner-settable, so if FWA moves its pool or the router needs replacing, it can't be done.\n- **Fix:** check only the shares held by Open rounds and the active round. A stake parked in the Open round can still block the owner, so pair the setters with pausing bets and a delay.\n\n**2. The owner can change the outcome of a round that's already in progress** (`src/KeelSpinVault.sol:711`)\n- **Cause:** `setIntake`, `setIMD`, `setOracleSigner` and `setOraclePayment` all refuse to run while a round is active. `setFloorOracle` and `setDecisionParameters` (haircut, margin, floor age, safety margin, fallback lead, default-keep) have no such check, and the decision reads them live.\n- **Repro:** backing is 1 ETH, so the ETH bid is 0.9 ETH. A fresh 0.5 ETH floor means the round should take the bid. The owner points the floor oracle at a fake one reporting 100 ETH, then anyone calls `requestDecision`. The round is kept on the obvious-floor path and the NFT goes to teamWallet. `setOracleSigner` in the same state reverts as it should.\n- **Impact:** the 0.9 ETH bid that belonged to the bettors becomes an NFT in teamWallet's custody (by default the owner). The bettors then depend on teamWallet returning sale proceeds, and any nonzero deposit settles the round. That breaks the brief's \"the owner can never move bettor ETH\".\n- **Fix:** add the same active-round check to both setters, or copy these values into the round at allocation, the way the fee and fee recipient are already locked in at round creation.\n\n**Checked and holding:**\n- **Oracle callbacks:** both accept only calls from Intake, for a request the vault made and hasn't settled. Results need a valid EIP-712 signature, enough panel agreement and a fresh, unexpired answer, and each is used once.\n- **Router:** only the vault can call `purchase`, and its refund is measured per purchase.\n- **Fees:** fee rate and recipient are fixed when a round is funded.\n- **Other setters:** every owner setter is owner-only.\n- **ETH movement:** the owner has no direct way to withdraw ETH from the vault.\n- **Pause:** pausing stops only new bets.\n\nThe slither and aderyn leads I looked at in this area (sending ETH to arbitrary users, weak randomness, missing zero checks) are intended behaviour here: the spin reward goes to whoever calls `spin`, and the blackout check is a time-of-day check, not randomness.\n\n**Not covered in depth:** rounding and arithmetic fuzzing, the on-chain JSON body encoding in `OracleBody.sol`, and how the real mainnet FWAV2 contract behaves. Everything was tested against the project's mocks only.","treeHash":null,"usage":{"cachedInputTokens":706334,"inputTokens":18,"model":"claude-opus-5-5","outputTokens":20082,"runtime":"claude","turns":11,"wallClockMs":227526}},{"artifacts":[],"attempt":1,"bundleHash":"1444ba83db021bd693c6c92ce6f3b491f1f8ba932d9b0334bc65ef631fed1807","device":"f768e94767a9dde3","findings":[],"hash":"37b9049d55d0aeef38c846534a1532438fc0eba9ba1449343d2ca025033626b2","nodeId":"cd76f658-9ab3-4f8e-9ce0-30407944f1c2","outcome":"completed","summary":"Both findings were real and are fixed. One caveat first: the command sandbox on this machine refused to start every shell command (it fails creating its user namespace before the command runs), so I could not run `forge build`, `forge test` or `forge fmt --check` here. The verifier will be the first to compile this revision. The changes are small, and I checked every new test line by line against the mocks it uses.\n\n**Finding d5f4aa5e (medium, stuck NFT after stranded settlement): fixed.** The stranded path marks the round kept while the token is still parked in the FWA pool, settles it 30 days later, and `recoverStuckNFT` then rejected the Settled round. Since FWA only lets the vault pull a stuck token, that locked it for good. `recoverStuckNFT` at src/KeelSpinVault.sol:643 now accepts the same states as `forwardNFT`: AwaitingSale, or Settled with the kept flag. An ETH-settled round is still refused. Two tests were added in test/KeelSettlement.t.sol:\n\n- The proof's scenario end to end: delivery fails, the team settles through the stranded path, recovery still reverts while the collection rejects, then recovers and forwards to the team, a repeat recovery reverts in the pool, and `forwardNFT` is a no-op afterwards.\n- Recovery on an ETH-settled round reverts with WrongState.\n\n**Finding 20472f8d (low, zero lead pays for an unanswerable ask): fixed.** The ask gate used a strict comparison while the callback refuses at or after `fallbackAt`, so a zero lead let an ask through at the exact instant no answer could be stored. The gate at src/KeelSpinVault.sol:432 now falls back when `block.timestamp + minOracleLead >= fallbackAt`, so a question is only paid for while strictly more than the configured lead remains, and the two boundaries agree for every lead. In test/KeelOracle.t.sol the two existing boundary tests moved by one second to the new boundary, the last-moment test now also delivers its answer one second before `fallbackAt`, and two zero-lead tests were added (no ask at `fallbackAt` with IMD unchanged and the default keep applied, and an ask one second earlier that is answered and stored).\n\n**Docs.** README.md, docs/SECURITY.md and docs/VALIDATION.md describe the recovery after a stranded settlement and the strict ask boundary. Answers to both findings are in `.imd-responses.json`, each marked fixed and noting that forge could not run on this machine.","treeHash":"617e816cc064469e3e45c859580a809d1b65b90d","usage":{"cachedInputTokens":1232162,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":22064,"runtime":"claude","turns":43,"wallClockMs":302289}},{"artifacts":[],"attempt":1,"bundleHash":"0a28a1a1f550cb127e3bde3bf90f1a00b168b9c22a99d57d3a37cb3f94b2b32a","device":"9b84a1e2e2a9ac5e","findings":[],"hash":"3af45e5fd441d8d7426b910232729fdbc91b8d94b3470ec453ec29f95a3d43d5","nodeId":"cd76f658-9ab3-4f8e-9ce0-30407944f1c2","outcome":"completed","summary":"Implemented both contracts, vendored dependencies, tests, and deployment documentation.\n\nPassed `forge build`, all 91 tests—including seeded fuzzing and solvency invariants—and `forge fmt --check`.\n\nDeployment settings, owner setup, and team sale responsibilities are documented in `README.md`.","treeHash":"1c5ad648e3d19cec17675c337acc2a5a9d65a360","usage":{"cachedInputTokens":7549568,"inputTokens":208582,"model":"gpt-6-astra","outputTokens":86642,"runtime":"codex","turns":12,"wallClockMs":2444751}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"63c29c49a249ab7e","findings":[{"citation":"resolved","description":"Boundary x invariant seam. Mainnet FWAV2 0x958C...6A98 returns settlementWindow() = 3600 (read via eth_call today; the fwa-v2 reference also says 3600 s), while the vault's default safetyMargin is 2 hours (line 94: `uint256 public safetyMargin = 2 hours;`) and fallbackLead 1 hour. Because window (3600) <= safetyMargin (7200), decisionDeadline() returns allocatedAt + 0, and fallbackAt() (line 364: `return deadline > allocatedAt + fallbackLead ? deadline - fallbackLead : allocatedAt;`) also returns allocatedAt. Consequences at default parameters: (1) requestDecision always takes `block.timestamp >= fallbackAt(id)` at line 404 and calls _fallback before any IMD request is paid; (2) executeDecision always reverts StaleAnswer (`block.timestamp >= decisionDeadline(id)`); (3) onOracleResult always reverts StaleAnswer. The oracle decision (step 2 of DECISION) never runs. Rounds with no fresh floor take `defaultKeep = true` and keep the NFT with no value information, giving up the 90% ETH bid even when the token has no buyers; rounds with a floor are decided only by the haircut floor. The test suite does not catch this because MockFWA uses settlementWindow = 24 hours (test/mocks/Protocols.sol:64). The same silent clamp hides any owner setting where safetyMargin + fallbackLead >= window: e.g. safety 30 min, lead 1 h gives fallbackAt = allocatedAt as well. Fix: do not clamp silently. Either reject (revert in setDecisionParameters / at allocation) configurations where safetyMargin + fallbackLead >= settlementWindow, or derive defaults that fit the live window (e.g. safety 10-15 min, lead 15-20 min), and add a test with window = 3600. Note: test/KeelOracle.t.sol testShortLiveWindowSaturatesAndDoesNotPayOracle asserts this saturation as intended, and the brief's literal defaults (2h margin, 1h lead) cause it. But the brief also requires step 2 (paid oracle) and FWA's live window makes it dead at deploy. So this is a conflict between the spec and the live protocol, and the owner needs to decide the scope (new defaults or a revert on unusable settings). It is not a silent clamp to ship.","line":358,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC721} from \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\nimport {IERC721} from \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {IFWAV2} from \"src/interfaces/IKeelProtocols.sol\";\n\ncontract DImd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\ncontract DNft is ERC721 {\n    constructor() ERC721(\"N\", \"N\") {}\n    function mint(address to, uint256 id) external { _mint(to, id); }\n}\n\ncontract DFloor {\n    function getFloor(address) external pure returns (uint256, uint48) { return (0, 0); }\n}\n\n\ncontract DIntake2 {\n    uint256 public calls;\n    struct Callback { address target; bytes4 selector; }\n    function priceOf(bytes32, address) external pure returns (uint256) { return 0.5 ether; }\n    function requestWithFailure(bytes32, bytes calldata, Callback calldata, bytes4, address asset, uint256 amount)\n        external payable returns (bytes32)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        return keccak256(abi.encode(++calls));\n    }\n}\n\n/// Minimal FWA V2 with the live mainnet settlementWindow (3600 s) and discount (9000).\ncontract DFwa {\n    uint256 public settlementWindow = 3600;\n    uint256 public settlementDiscountBps = 9000;\n    uint256 public fee = 0.064 ether;\n    uint256 nextReq;\n    uint256 nextListing;\n    mapping(uint256 => IFWAV2.Acquisition) acq;\n    mapping(uint256 => IFWAV2.Listing) lst;\n    mapping(uint256 => address) public stuckNFTRecipient;\n\n    function quoteAcquisitionPrice() external view returns (uint256, uint256, uint256) { return (fee, 0, fee); }\n    function isPurchaseBlackout() external pure returns (bool) { return false; }\n    function acquisitions(uint256 id) external view returns (IFWAV2.Acquisition memory) { return acq[id]; }\n    function listings(uint256 id) external view returns (IFWAV2.Listing memory) { return lst[id]; }\n    function acquire(address purchaser, uint256, uint256, uint256, uint256) external payable returns (uint256[] memory ids) {\n        ids = new uint256[](1);\n        ids[0] = ++nextReq;\n        acq[ids[0]] = IFWAV2.Acquisition(purchaser, block.number, fee, 0, 1);\n    }\n    function allocate(uint256 req, DNft nft, uint256 tokenId, uint256 backing) external {\n        nft.mint(address(this), tokenId);\n        uint256 l = ++nextListing;\n        lst[l] = IFWAV2.Listing(address(nft), address(0xD3F0), acq[req].purchaser, tokenId, 1, backing, 0, 0, 0, uint64(block.timestamp), 2);\n        acq[req].listingId = l;\n        acq[req].status = 2;\n    }\n    function keepNFT(uint256 id) external {\n        IFWAV2.Listing storage l = lst[id];\n        require(msg.sender == l.purchaser && l.status == 2);\n        l.status = 4;\n        IERC721(l.collection).safeTransferFrom(address(this), l.purchaser, l.tokenId);\n    }\n    function acceptDepositorBid(uint256 id) external {\n        IFWAV2.Listing storage l = lst[id];\n        require(msg.sender == l.purchaser && l.status == 2);\n        l.status = 4;\n        (bool ok,) = l.purchaser.call{value: l.value * settlementDiscountBps / 10000}(\"\");\n        require(ok);\n    }\n    receive() external payable {}\n}\n\ncontract DeadlineCollapseTest is Test {\n    function test_oraclePathReachableWithLiveOneHourWindow() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        DFwa pool = new DFwa();\n        DImd imd = new DImd();\n        DIntake2 intake = new DIntake2();\n        DNft nft = new DNft();\n        address team = address(0x7EA7);\n        KeelSpinVault vault = new KeelSpinVault(\n            address(this), address(pool), address(new DFloor()), address(intake), address(imd), address(0x5598), team\n        );\n        KeelFwaRouter router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        imd.mint(address(vault), 10 ether);\n        vm.deal(address(pool), 100 ether);\n\n        address alice = address(0xA11CE);\n        vm.deal(alice, 10 ether);\n        uint256 id = vault.currentRoundId();\n        uint256 target = vault.liveTarget();\n        vm.prank(alice);\n        vault.bet{value: target}();\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether);\n        vault.syncRound(id);\n\n        uint256 allocatedAt = vault.rounds(id).allocatedAt;\n        // Right after allocation, with ~an hour left before FWA's settlement window closes,\n        // the vault should still have time for the paid oracle answer.\n        assertGt(vault.decisionDeadline(id), allocatedAt, \"decision deadline collapsed to allocatedAt\");\n        assertGt(vault.fallbackAt(id), allocatedAt, \"fallback cutoff collapsed to allocatedAt\");\n\n        vault.requestDecision(id);\n        // Expected: no fresh floor, so the 0.5 IMD oracle request is paid and the round waits for it.\n        // Actual: requestDecision falls straight to settleFallback's defaultKeep and keeps the NFT blind.\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.OraclePending), \"oracle never asked\");\n        assertEq(intake.calls(), 1);\n    }\n    receive() external payable {}\n}","reproduction":"Live FWA settlementWindow = 3600, default safetyMargin = 7200, fallbackLead = 3600. Fund a round (bet liveTarget), spin, FWA allocates listing (allocatedAt = T, backing 1 ETH), syncRound. Then decisionDeadline(id) == T and fallbackAt(id) == T. requestDecision(id) at T with no fresh floor: expected the 0.5 IMD oracle request is paid and the round is OraclePending (about 60 minutes remain before FWA's window closes); actual: _fallback runs immediately, defaultKeep keeps the NFT (round -> AwaitingSale), no IMD spent, Decision path = FallbackDefault. With safetyMargin=15 min and fallbackLead=20 min the same test passes (state OraclePending, one intake call), confirming the clamp is the cause.","severity":"medium","snippet":"        return uint256(r.allocatedAt) + (window > safetyMargin ? window - safetyMargin : 0);","title":"decisionDeadline clamps to allocatedAt with the live 1-hour FWA settlementWindow, so the paid oracle path is unreachable and every round is decided blind by fallback"}],"hash":"41ddf1e50c392c58713919eeb8e59529848cf584bb17f524111477ecd3c8b906","nodeId":"c9f45422-af9f-4cc2-9d6a-e1f1c09df1db","outcome":"completed","summary":"The math review found one defect, rated medium. Because of it, the paid oracle decision never runs on mainnet with the default settings. `.imd-findings.json` holds that finding and 46 coverage rows: all 45 entry points plus the solvency invariant. I changed no files; the scratch test I used is deleted.\n\n**Finding 1 (medium): the decision deadline collapses to the moment the NFT is allocated**\n\n`src/KeelSpinVault.sol:358`:\n```solidity\nreturn uint256(r.allocatedAt) + (window > safetyMargin ? window - safetyMargin : 0);\n```\n- **The numbers:** live FWAV2 returns `settlementWindow() = 3600` (one hour; I read it from mainnet). The vault's default `safetyMargin` is 2 hours and `fallbackLead` is 1 hour. Since 3600 ≤ 7200, `decisionDeadline` returns `allocatedAt`, and `fallbackAt` (line 364) returns `allocatedAt` too.\n- **What happens:**\n  - `requestDecision` goes straight to the fallback without paying the 0.5 IMD oracle request.\n  - `executeDecision` and `onOracleResult` always revert with `StaleAnswer`.\n  - With no fresh floor price, `defaultKeep` keeps the NFT with no information about its value. That gives up the 90% ETH bid even when the token has no buyers.\n- **Why the tests miss it:** the mock pool uses a 24-hour window. The existing test `testShortLiveWindowSaturatesAndDoesNotPayOracle` asserts this collapse as intended behaviour.\n- **Why it's a spec conflict:** the brief's own defaults (2h margin, 1h lead) produce this result. But the brief also requires the paid-oracle step, and the live one-hour window makes that step dead from deployment.\n- **Proof:** I attached a self-contained Foundry test. It fails on the current code (\"decision deadline collapsed to allocatedAt: 1800061200 <= 1800061200\"). With a 15-minute margin and 20-minute lead it passes: the round reaches `OraclePending` and the intake is called once.\n- **Fix:** reject settings where margin plus lead is at least the FWA window, or choose defaults that fit a one-hour window. The owner needs to decide which.\n\n**What I checked that holds**\n- **Share pricing:** after a refund, new bettors buy at the reduced price, rounding down in favour of existing bettors. A round with shares but zero cash can't happen, because the refunded pool fee is never zero.\n- **Pro-rata claims and withdrawals:** they round down, and the last claimer gets the remainder, so no dust is stuck.\n- **Fees:** charged only on proceeds or the bid, never on the buffer or refunds.\n- **Spin reward:** capped at 2% of the quote (`quote/50`). The refund calculation in `spin` is exact.\n- **Decision comparisons:** the obvious-keep, oracle-margin and fallback-floor comparisons match the brief, and none can overflow (offers are capped at 1e22, backing at 2^128).\n- **Blackout:** the `% 12 hours` check matches 11:45–12:00 and 23:45–00:00 UTC.\n- **Solvency:** the vault's tracked ETH never exceeds its balance. Every decrement comes before the transfer it pays for, and every credit comes from an observed balance change or `msg.value`.\n- **Not reported:** `TooManyRounds` on a bet larger than 32 round targets is intentional and already tested.\n\n**Coverage limits**\n- I covered the router's builder-reward paths only for whether they touch vault ETH.\n- I didn't check whether the oracle intake accepts the brief-mandated `toleranceBps` and `guards` keys in the request body. The oracle reference doesn't list them, and it says an unknown key makes the intake refuse the whole request.","treeHash":null,"usage":{"cachedInputTokens":1654068,"inputTokens":38,"model":"claude-opus-5-5","outputTokens":25004,"runtime":"claude","turns":20,"wallClockMs":302263}},{"artifacts":[],"attempt":1,"bundleHash":"31238594d7238566817bfd1feac7868f62e964a9dd335da498299007ccc71830","device":"0b9c1289a6def0a1","findings":[],"hash":"5992161059ab592bd998d2fd9b700944b2f37a25c5a76991ede6a8be320d8a4b","nodeId":"1fc1ea14-b5d3-487e-9e2c-c4904db0ba05","outcome":"completed","summary":"Added failure-path tests, fuzz coverage for up to 500 bettors, and vault/router invariants covering 20,480 random calls.\n\n`forge build` passed. Full suite with the required seed: **115 passed, 0 failed, 0 skipped**.\n\nOnly `test/` files were added. No confirmed implementation defects were reproduced. Tests run offline; mainnet fork validation remains outstanding.","treeHash":"aa243ad585d4724a01ebb48e2b8c2aab1b824599","usage":{"cachedInputTokens":4775552,"inputTokens":165884,"model":"gpt-6-astra","outputTokens":33649,"runtime":"codex","turns":10,"wallClockMs":1039508}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ee2cc12e7dfe571a","findings":[{"citation":"resolved","description":"Introduced by the revision that fixed the stranded-NFT advisory (1f40d75b). When FWA's best-effort delivery fails, the listing's stuckNFTRecipient is the vault and _reconcileOutcome marks the round kept (line 555) although the NFT never left the pool. nftStranded(id) becomes true 30 days after allocation (line 617) and depositSaleProceeds then settles the round without nftForwarded (line 649). After that, recoverStuckNFT(id) reverts WrongState at line 640 because it accepts only AwaitingSale, and forwardNFT (which now accepts Settled && kept) only emits NFTForwardPending because _ownsNFT is false. FWA's recoverStuckNFT reverts NotStuckRecipient for any caller other than the vault (fwa-v2 reference, errors list and item 7), so no other address can pull the token. The NFT, which is the round's backing value, is stranded in the FWA pool for good, while docs/SECURITY.md says the kept NFT remains forwardable after a stranded settlement. Merged from the independent tester's finding 75f0532b; its proof fails on this tree with WrongState(). Minimal fix: let recoverStuckNFT also run when r.state == Settled && r.kept (and optionally have forwardNFT call pool.recoverStuckNFT when stuckNFTRecipient(listingId) == address(this)), or refuse the stranded settlement while stuckNFTRecipient(listingId) == address(this).","line":640,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {MockFWA, MockNFT, MockIMD, MockFloor, MockIntake} from \"test/mocks/Protocols.sol\";\n\n/// A kept NFT that FWA could not deliver (stuckNFTRecipient == vault) and that the team settles through the\n/// 30-day stranded path can never be recovered afterwards: recoverStuckNFT requires AwaitingSale.\ncontract StrandedStuckProof is Test {\n    KeelSpinVault vault;\n    KeelFwaRouter router;\n    MockFWA pool;\n    MockNFT nft;\n    address alice = address(0xA11CE);\n    address team = address(0x7EA7);\n    address depositor = address(0xD3F0);\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        pool = new MockFWA();\n        nft = new MockNFT();\n        MockIMD imd = new MockIMD();\n        MockFloor floor = new MockFloor();\n        MockIntake intake = new MockIntake();\n        vault = new KeelSpinVault(\n            address(this), address(pool), address(floor), address(intake), address(imd), vm.addr(0xA77E57), team\n        );\n        router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        vm.deal(address(pool), 10000 ether);\n        vm.deal(alice, 10000 ether);\n        imd.mint(address(vault), 100 ether);\n    }\n\n    function testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() public {\n        uint256 id = vault.currentRoundId();\n        vm.prank(alice);\n        vault.bet{value: vault.liveTarget()}();\n        vault.spin(id);\n        uint256 listingId = pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether, depositor);\n        vault.syncRound(id);\n\n        // FWA's best-effort delivery fails: the NFT stays in the pool, stuck for the vault.\n        nft.setRejectTransfers(true);\n        vm.warp(block.timestamp + pool.settlementWindow());\n        pool.finalizeUnsettled(listingId);\n        vault.syncRound(id);\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.AwaitingSale));\n        assertEq(pool.stuckNFTRecipient(listingId), address(vault));\n\n        // 30 days later the team uses the documented stranded path to release the round's cash.\n        vm.warp(uint256(vault.rounds(id).allocatedAt) + 30 days);\n        assertTrue(vault.nftStranded(id));\n        vm.deal(team, 1 ether);\n        vm.prank(team);\n        vault.depositSaleProceeds{value: 1}(id, keccak256(\"stranded\"));\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.Settled));\n\n        // The collection becomes transferable again. The NFT must still be recoverable and forwardable\n        // (\"the NFT remains forwardable\"), but recoverStuckNFT reverts WrongState on a Settled round.\n        nft.setRejectTransfers(false);\n        vault.recoverStuckNFT(id);\n        vault.forwardNFT(id);\n        assertEq(nft.ownerOf(1), team, \"kept NFT is permanently stuck in the FWA pool\");\n    }\n\n    receive() external payable {}\n}","reproduction":"MockFWA (24 h window): alice bets liveTarget, spin(id), pool.allocate(requestId, nft, 1, 1 ether, depositor), syncRound(id). nft.setRejectTransfers(true); warp +settlementWindow; pool.finalizeUnsettled(listingId) so stuckNFTRecipient(listingId) == vault; syncRound(id) -> state AwaitingSale, kept == true, nftForwarded == false. Warp to allocatedAt + 30 days: nftStranded(id) == true; team calls depositSaleProceeds{value: 1}(id, h) -> Settled. nft.setRejectTransfers(false); vault.recoverStuckNFT(id). Expected: the pool releases the NFT to the vault and forwardNFT sends it to teamWallet (nft.ownerOf(1) == team). Actual: recoverStuckNFT reverts WrongState(); forwardNFT emits NFTForwardPending and the NFT stays in the pool forever. Ran test/scratch/Proof_75f0532b44ac.t.sol on this tree: FAIL [WrongState()] testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement.","severity":"medium","snippet":"        if (r.state != State.AwaitingSale) revert WrongState();","title":"A kept NFT still stuck in the FWA pool becomes unrecoverable once the round is settled through the 30-day stranded path, because recoverStuckNFT requires AwaitingSale"},{"citation":"resolved","description":"The revision that fixed advisory 8c7b75fd gates the paid ask on block.timestamp + minOracleLead > fallbackAt(id), while onOracleResult refuses every delivery once block.timestamp >= fallbackAt(id) (line 469). The two comparisons disagree at the boundary: with oracleLead == 0, which setDecisionParameters accepts (lines 820-822 only cap it at 30 days), an ask at block.timestamp == fallbackAt passes the gate and spends oraclePrice on a question whose answer can never be stored. The default 10-minute lead is not affected, so this depends on owner configuration; the cost is at most one paid ask per round. Merged from the tester's finding 0f018e5b. Fix: use >= in the gate, or require oracleLead > 0 in setDecisionParameters.","line":431,"path":"src/KeelSpinVault.sol","reproduction":"owner: setDecisionParameters(1500, 500, 24 hours, 10 minutes, 20 minutes, 0, true). alice bets liveTarget, spin(id), pool.allocate(..., 1 ether, depositor), syncRound(id), no floor set, 100 IMD in the vault. vm.warp(vault.fallbackAt(id)); anyone calls requestDecision(id). Expected: the fallback rule runs and imdBalance is unchanged. Actual: state == OraclePending (4) and imdBalance drops by 500000000000000000; any later onOracleResult reverts StaleAnswer because block.timestamp >= fallbackAt(id). Ran test/scratch/LeadZero.t.sol: FAIL 'IMD spent on an unanswerable request: 500000000000000000 != 0'.","severity":"low","snippet":"        if (r.oracleFailed || block.timestamp + minOracleLead > fallbackAt(id)) {","title":"With minOracleLead set to 0, requestDecision still pays 0.5 IMD at exactly fallbackAt for an answer onOracleResult must refuse"}],"hash":"6692d943dac7b262555d203ea84b648503ef1755ddbf3d22e8d4ddd99b5c6069","nodeId":"f141be15-15f0-49d6-b60d-2c039f61704a","outcome":"completed","summary":"The review is complete. The findings file is written at `.imd-findings.json` with 2 findings and a 46-row coverage record.\n\n**Settlement of the previous round.** All four blocking findings and all four advisories are fixed. I ran every attached proof against the revised tree and each one now passes, and I read the full source diff of the fix commit.\n\n- **Collapsed decision deadline (8165f5b9):** fixed. Defaults are now 10 min safety, 20 min lead, 10 min oracle lead, and setDecisionParameters rejects a sum at or above the live settlement window. Proof passes.\n- **Unclaimed Settled shares block migration (635de21b):** fixed. A new unresolvedShares counter is released at reconciliation and gates setFwa, setRouter and router.setPool. Proof passes.\n- **Unguarded setFloorOracle and setDecisionParameters (3a71b04a):** fixed. Both setters now call _noActive.\n- **Advisories (8c7b75fd, 1f40d75b, edde118a, a2827a1f):** all fixed as the author described.\n\n**What the revision introduced.** The independent tester's finding reproduces, and I kept it as the one medium finding.\n\n1. **[medium] Stuck NFT unrecoverable after a stranded settlement**, `src/KeelSpinVault.sol:640`. The new 30-day stranded path lets the team settle a kept round while the NFT is still held by the FWA pool with the vault as stuckNFTRecipient. Afterwards recoverStuckNFT reverts WrongState because it accepts only AwaitingSale, and FWA refuses any other caller. The NFT is lost for good. The tester's proof fails on this tree with WrongState and is attached.\n2. **[low] minOracleLead of 0 still pays at exactly fallbackAt**, `src/KeelSpinVault.sol:431`. The ask gate uses a strict comparison while onOracleResult refuses at or after fallbackAt, so an owner who sets the lead to 0 spends 0.5 IMD on an unanswerable question. Reproduced with a scratch test; the default is unaffected.\n\n**Verification state.** The full project suite passes with 133 tests and 0 failures. Every listed entry point has a coverage row, 42 hold, 4 point at the two findings, and one row records the accounting invariant.","treeHash":null,"usage":{"cachedInputTokens":1577791,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":20799,"runtime":"claude","turns":17,"wallClockMs":379930}},{"artifacts":[],"attempt":1,"bundleHash":"ffaa3c18ddcf4592f8c4d54b0942e364d4126614e5ff1ee6b56797a0dda4e771","device":"9b657b3675e34ec5","findings":[],"hash":"6e61cdf13d63c5e194966570d7bba1f48e7c39a19cf9bde8bd5609efd3579cbc","nodeId":"cd76f658-9ab3-4f8e-9ce0-30407944f1c2","outcome":"completed","summary":"The responses file exists and is excluded by the harness's own `.git/info/exclude` entry, alongside `.imd/`, so that is deliberate and the harness reads it from disk. All work is complete.\n\n## Recap\n\nAll three reopening findings reproduced and are fixed. Both reviewer proofs now pass on the result, the full suite is green, and `forge build`, `forge test` and `forge fmt --check` pass with the pinned compiler.\n\n| Check | Result |\n| --- | --- |\n| Own tests | 102 passed |\n| Reviewer proofs (from scratch copy) | 2 passed |\n| Vault runtime | 24,528 bytes |\n\n**What changed in `src/KeelSpinVault.sol`**\n\n- **Deadline collapse.** Defaults are now 10 min safety margin, 20 min fallback lead and a new 10 min minimum oracle lead, sized for the live one-hour FWAV2 window. The decision setter reads the pool's live window and rejects a timing sum that does not fit, so the collapse cannot be reconfigured silently.\n- **Migration blocked by unclaimed stake.** A new `unresolvedShares` counter covers only Open and in-flight rounds. The vault's pool and router setters and the router's pool setter use it, so an unclaimed payout in a Settled round never freezes the owner-settable wiring.\n- **Owner steering an allocated round.** The floor oracle and decision policy setters now revert while a spin is unresolved, matching the Intake, signer, IMD and payment setters.\n- **Advisory items, all judged real and fixed.** No paid oracle ask when the answer could not land before the fallback cutoff. A kept NFT the collection never releases unlocks the round's cash after a 30-day grace while staying forwardable. Calls on already-resolved rounds retry the forward instead of reverting. The ETH-path fee base is capped at the bid, so unsolicited ETH is not taxed.\n\n**One thing to be aware of.** The added code pushed the vault past the EIP-170 size limit at 800 optimizer runs. Lower values made the compiler move constants into a trailing CODECOPY data region, which the opcode scan misreads. I settled on 700 runs in `foundry.toml`, which fits the limit with the runtime ending in code as before, and consolidated the per-parameter emits in the three multi-parameter setters into one event each. The remaining size headroom is small, so any further vault additions will need trimming elsewhere.\n\nDocumentation in the README, `docs/SECURITY.md` and `docs/VALIDATION.md` describes the new timing defaults, the departure from the brief's literal two-hour and one-hour figures, the migration rule, the grace path and the optimizer-runs rationale. Every finding is answered in `.imd-responses.json`.","treeHash":"7125894dd6c8c22604473049cdb672de1d767904","usage":{"cachedInputTokens":4314805,"inputTokens":866,"model":"claude-fable-5-1","outputTokens":64838,"runtime":"claude","turns":82,"wallClockMs":1627091}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4f2ce49dc630c86e","findings":[{"citation":"resolved","description":"Merges audit_flow #3, audit_economics #2 and audit_math #1, which share one root cause. FWAV2 settlementWindow() is 3600 s on mainnet (fwa-v2 reference §6). The defaults are safetyMargin = 2 hours (line 94) and fallbackLead = 1 hours (line 95). decisionDeadline therefore clamps silently to allocatedAt, and fallbackAt (line 364) does too. The results: requestDecision always takes the `block.timestamp >= fallbackAt(id)` branch at line 404 and calls _fallback without asking the Intake; onOracleResult always reverts StaleAnswer (line 442); executeDecision always reverts StaleAnswer (line 480). Step 2 of DECISION (the 0.5 IMD panel valuation compared against ethBid*(1+margin)) is dead at deployment. A collection with no fresh floor is kept through defaultKeep=true with no valuation at all, giving up the 90% ETH bid. The test suite misses this because MockFWA uses a 24 h window. The brief's literal defaults conflict with the live protocol, so the owner has to decide the scope. Options: defaults that fit the window (for example 10-15 min margin and 15-20 min lead), or rejecting safetyMargin + fallbackLead >= settlementWindow instead of clamping silently.","line":358,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC721} from \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\nimport {IERC721} from \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {IFWAV2} from \"src/interfaces/IKeelProtocols.sol\";\n\ncontract DImd is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\ncontract DNft is ERC721 {\n    constructor() ERC721(\"N\", \"N\") {}\n    function mint(address to, uint256 id) external { _mint(to, id); }\n}\n\ncontract DFloor {\n    function getFloor(address) external pure returns (uint256, uint48) { return (0, 0); }\n}\n\n\ncontract DIntake2 {\n    uint256 public calls;\n    struct Callback { address target; bytes4 selector; }\n    function priceOf(bytes32, address) external pure returns (uint256) { return 0.5 ether; }\n    function requestWithFailure(bytes32, bytes calldata, Callback calldata, bytes4, address asset, uint256 amount)\n        external payable returns (bytes32)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        return keccak256(abi.encode(++calls));\n    }\n}\n\n/// Minimal FWA V2 with the live mainnet settlementWindow (3600 s) and discount (9000).\ncontract DFwa {\n    uint256 public settlementWindow = 3600;\n    uint256 public settlementDiscountBps = 9000;\n    uint256 public fee = 0.064 ether;\n    uint256 nextReq;\n    uint256 nextListing;\n    mapping(uint256 => IFWAV2.Acquisition) acq;\n    mapping(uint256 => IFWAV2.Listing) lst;\n    mapping(uint256 => address) public stuckNFTRecipient;\n\n    function quoteAcquisitionPrice() external view returns (uint256, uint256, uint256) { return (fee, 0, fee); }\n    function isPurchaseBlackout() external pure returns (bool) { return false; }\n    function acquisitions(uint256 id) external view returns (IFWAV2.Acquisition memory) { return acq[id]; }\n    function listings(uint256 id) external view returns (IFWAV2.Listing memory) { return lst[id]; }\n    function acquire(address purchaser, uint256, uint256, uint256, uint256) external payable returns (uint256[] memory ids) {\n        ids = new uint256[](1);\n        ids[0] = ++nextReq;\n        acq[ids[0]] = IFWAV2.Acquisition(purchaser, block.number, fee, 0, 1);\n    }\n    function allocate(uint256 req, DNft nft, uint256 tokenId, uint256 backing) external {\n        nft.mint(address(this), tokenId);\n        uint256 l = ++nextListing;\n        lst[l] = IFWAV2.Listing(address(nft), address(0xD3F0), acq[req].purchaser, tokenId, 1, backing, 0, 0, 0, uint64(block.timestamp), 2);\n        acq[req].listingId = l;\n        acq[req].status = 2;\n    }\n    function keepNFT(uint256 id) external {\n        IFWAV2.Listing storage l = lst[id];\n        require(msg.sender == l.purchaser && l.status == 2);\n        l.status = 4;\n        IERC721(l.collection).safeTransferFrom(address(this), l.purchaser, l.tokenId);\n    }\n    function acceptDepositorBid(uint256 id) external {\n        IFWAV2.Listing storage l = lst[id];\n        require(msg.sender == l.purchaser && l.status == 2);\n        l.status = 4;\n        (bool ok,) = l.purchaser.call{value: l.value * settlementDiscountBps / 10000}(\"\");\n        require(ok);\n    }\n    receive() external payable {}\n}\n\ncontract DeadlineCollapseTest is Test {\n    function test_oraclePathReachableWithLiveOneHourWindow() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        DFwa pool = new DFwa();\n        DImd imd = new DImd();\n        DIntake2 intake = new DIntake2();\n        DNft nft = new DNft();\n        address team = address(0x7EA7);\n        KeelSpinVault vault = new KeelSpinVault(\n            address(this), address(pool), address(new DFloor()), address(intake), address(imd), address(0x5598), team\n        );\n        KeelFwaRouter router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        imd.mint(address(vault), 10 ether);\n        vm.deal(address(pool), 100 ether);\n\n        address alice = address(0xA11CE);\n        vm.deal(alice, 10 ether);\n        uint256 id = vault.currentRoundId();\n        uint256 target = vault.liveTarget();\n        vm.prank(alice);\n        vault.bet{value: target}();\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether);\n        vault.syncRound(id);\n\n        uint256 allocatedAt = vault.rounds(id).allocatedAt;\n        // Right after allocation, with ~an hour left before FWA's settlement window closes,\n        // the vault should still have time for the paid oracle answer.\n        assertGt(vault.decisionDeadline(id), allocatedAt, \"decision deadline collapsed to allocatedAt\");\n        assertGt(vault.fallbackAt(id), allocatedAt, \"fallback cutoff collapsed to allocatedAt\");\n\n        vault.requestDecision(id);\n        // Expected: no fresh floor, so the 0.5 IMD oracle request is paid and the round waits for it.\n        // Actual: requestDecision falls straight to settleFallback's defaultKeep and keeps the NFT blind.\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.OraclePending), \"oracle never asked\");\n        assertEq(intake.calls(), 1);\n    }\n    receive() external payable {}\n}","reproduction":"Pool settlementWindow = 3600, defaults safetyMargin 7200 and fallbackLead 3600. Fund a round to liveTarget, spin, have FWA allocate a listing (allocatedAt = T, backing 1 ETH), then syncRound. Expected: decisionDeadline(id) > T, and requestDecision at T with no fresh floor pays 0.5 IMD and moves the round to OraclePending. Actual: decisionDeadline(id) == fallbackAt(id) == T, and requestDecision runs _fallback straight away (no IMD spent, path FallbackDefault, NFT kept). Ran .imd/reads/proofs/Proof_96f9d4357d30.t.sol (copied to test/scratch): FAIL 'decision deadline collapsed to allocatedAt: 1800061200 <= 1800061200'.","severity":"medium","snippet":"        return uint256(r.allocatedAt) + (window > safetyMargin ? window - safetyMargin : 0);","title":"With the live 1-hour FWAV2 settlementWindow, the default safetyMargin (2h) and fallbackLead (1h) collapse decisionDeadline and fallbackAt to allocatedAt, so the paid oracle decision can never run"},{"citation":"resolved","description":"Merges audit_economics #1 and audit_permissions #1. totalSharesOutstanding only falls in _burn (withdraw or claim). A Settled round no longer reads fwa or the router, but its unclaimed shares still count. setFwa and setRouter (via _noBets) and KeelFwaRouter.setPool (`IKeelVault(vault).totalSharesOutstanding() != 0`, src/KeelFwaRouter.sol:57) therefore revert for as long as any past bettor has not claimed. Any address can freeze the owner-settable FWA/router wiring the brief requires for 0.001 ETH, and the owner has no way around it. Fix: count only shares of Open rounds and of the active round. For example, keep an unresolved-shares counter that is decremented when a round reaches AwaitingSale or Settled.","line":825,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {IFWAV2} from \"src/interfaces/IKeelProtocols.sol\";\n\n/// Minimal self-contained FWA stand-in: quote, acquire, one allocation, ETH-bid settlement.\ncontract TinyFWA is IFWAV2 {\n    uint256 public settlementDiscountBps = 9000;\n    uint256 public settlementWindow = 24 hours;\n    uint256 public nextRequest;\n    mapping(uint256 => Acquisition) internal _acq;\n    mapping(uint256 => Listing) internal _list;\n    mapping(address => uint256) public acquisitionRefundCredit;\n    mapping(uint256 => address) public stuckNFTRecipient;\n\n    function quoteAcquisitionPrice() external pure returns (uint256, uint256, uint256) {\n        return (0.1 ether, 0.003 ether, 0.103 ether);\n    }\n\n    function isPurchaseBlackout() external pure returns (bool) {\n        return false;\n    }\n\n    function acquire(address purchaser, uint256, uint256, uint256, uint256) external payable returns (uint256[] memory ids) {\n        ids = new uint256[](1);\n        ids[0] = ++nextRequest;\n        _acq[ids[0]] = Acquisition(purchaser, block.number, 0.1 ether, 0, 1);\n    }\n\n    function allocate(uint256 requestId, address collection, uint256 tokenId, uint256 backing) external {\n        Acquisition storage a = _acq[requestId];\n        _list[1] = Listing(collection, address(0xD3F0), a.purchaser, tokenId, 1, backing, 0, 0, 0, uint64(block.timestamp), 2);\n        a.listingId = 1;\n        a.status = 2;\n    }\n\n    function acquisitions(uint256 id) external view returns (Acquisition memory) {\n        return _acq[id];\n    }\n\n    function listings(uint256 id) external view returns (Listing memory) {\n        return _list[id];\n    }\n\n    function acceptDepositorBid(uint256 id) external {\n        Listing storage l = _list[id];\n        require(l.status == 2 && msg.sender == l.purchaser, \"not purchaser\");\n        l.status = 4;\n        (bool ok,) = payable(l.purchaser).call{value: l.value * settlementDiscountBps / 10000}(\"\");\n        require(ok, \"pay\");\n    }\n\n    function keepNFT(uint256) external pure {\n        revert(\"unused\");\n    }\n\n    function withdrawAcquisitionRefund() external pure returns (uint256) {\n        revert(\"unused\");\n    }\n\n    function recoverStuckNFT(uint256) external pure {\n        revert(\"unused\");\n    }\n\n    function rewards() external pure returns (address) {\n        return address(0);\n    }\n\n    function token() external pure returns (address) {\n        return address(0);\n    }\n\n    receive() external payable {}\n}\n\ncontract TinyCollection {\n    function ownerOf(uint256) external view returns (address) {\n        return address(this);\n    }\n}\n\ncontract TinyFloor {\n    function getFloor(address) external view returns (uint256, uint48) {\n        return (0.5 ether, uint48(block.timestamp));\n    }\n}\n\n/// A single never-claimed 0.001 ETH stake in a Settled round keeps totalSharesOutstanding != 0 forever,\n/// so setFwa / setRouter / router.setPool revert OutstandingBets although no Open or active round exists.\ncontract MigrationBlockedTest is Test {\n    KeelSpinVault vault;\n    KeelFwaRouter router;\n    TinyFWA pool;\n    TinyFloor floor;\n    TinyCollection collection;\n    address alice = address(0xA11CE);\n    address griefer = address(0x6B1E);\n    address team = address(0x7EA7);\n    address keeper = address(0xB07);\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        pool = new TinyFWA();\n        floor = new TinyFloor();\n        collection = new TinyCollection();\n        vault = new KeelSpinVault(\n            address(this), address(pool), address(floor), address(0x1A7A), address(0x1D), address(0xBEEF), team\n        );\n        router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        vm.deal(address(pool), 1000 ether);\n        vm.deal(alice, 100 ether);\n        vm.deal(griefer, 1 ether);\n        vm.deal(keeper, 1 ether);\n    }\n\n    function testSettledUnclaimedStakeMustNotBlockPoolMigration() public {\n        uint256 id = vault.currentRoundId();\n        // griefer enters first with the minimum bet, alice fills the rest of the round\n        vm.prank(griefer);\n        vault.bet{value: 0.001 ether}();\n        uint256 target = vault.liveTarget();\n        vm.prank(alice);\n        vault.bet{value: target - 0.001 ether}();\n        vm.prank(keeper);\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, address(collection), 1, 1 ether);\n        vault.syncRound(id);\n        // floor 0.5 ETH haircut 0.425 < bid 0.9 * 1.05 -> ETH path at fallbackAt\n        vm.warp(vault.fallbackAt(id));\n        vault.settleFallback(id);\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.Settled));\n        vm.prank(alice);\n        vault.claim(id);\n        // griefer never claims. No round is Open with shares, none is active.\n        assertEq(vault.activeRoundId(), 0);\n        assertEq(vault.rounds(vault.currentRoundId()).totalShares, 0);\n        assertGt(vault.stake(id, griefer), 0);\n\n        // Expected: the owner can still migrate the pool and router (nothing funded depends on them).\n        TinyFWA newPool = new TinyFWA();\n        KeelFwaRouter newRouter = new KeelFwaRouter(address(this), address(vault), address(newPool));\n        vault.setFwa(address(newPool));\n        vault.setRouter(address(newRouter));\n        assertEq(address(vault.fwa()), address(newPool));\n        // and the griefer's claim is unaffected\n        vm.prank(griefer);\n        vault.claim(id);\n    }\n}","reproduction":"A griefer bets 0.001 ETH into round 1 and alice fills the round. Spin, allocate, then settleFallback with a low floor so the ETH path settles round 1. Alice claims and the griefer never does. Now activeRoundId == 0 and totalSharesOutstanding == 0.001e18. owner setFwa(newPool) should succeed but reverts OutstandingBets(); router.setPool reverts the same way. Ran .imd/reads/proofs/Proof_c11f439b35a2.t.sol: FAIL OutstandingBets().","severity":"medium","snippet":"        if (totalSharesOutstanding != 0 || activeRoundId != 0) revert OutstandingBets();","title":"Unclaimed shares of Settled rounds count in totalSharesOutstanding forever, so one unclaimed min bet blocks setFwa, setRouter and router.setPool permanently"},{"citation":"resolved","description":"From audit_permissions #2. setIntake, setIMD, setOracleSigner and setOraclePayment all call _noActive(). setFloorOracle (line 711) and setDecisionParameters (line 774: haircut, margin, floorAge, safetyMargin, fallbackLead, defaultKeep) do not, and requestDecision, _fallback and executeDecision read all of them live. Once a round is Allocated, the owner can pick its outcome by pointing floorOracle at a contract that returns a large fresh floor, or by setting haircut 0 or defaultKeep true with a tiny maxFloorAge. The NFT then goes to teamWallet (default $owner) instead of the ETH bid going to the round. The bettors' value then depends on the owner depositing proceeds, and depositSaleProceeds accepts any nonzero amount. This weakens 'the owner can never move bettor ETH'. Fix: add _noActive() to both setters, or snapshot these parameters into the Round at allocation, the way fee and feeRecipient are snapshotted.","line":711,"path":"src/KeelSpinVault.sol","reproduction":"Allocated round with backing 1 ETH (ethBid 0.9 ETH) and a real fresh floor of 0.5 ETH, so the expected outcome is the ETH bid (0.425 < 0.945, not obvious). The owner calls setFloorOracle(fake), where fake.getFloor returns (100 ether, block.timestamp). It succeeds, while setOracleSigner(x) in the same state reverts SpinInProgress. Anyone calls requestDecision(id): 100*0.85 >= 0.9*3, so _resolve(keep, ObviousFloor) runs and nft.ownerOf(tokenId) == teamWallet. Expected: the setter reverts while activeRoundId != 0 and the round takes the 0.9 ETH bid.","severity":"medium","snippet":"    function setFloorOracle(address to) external onlyOwner {\n        _checkAddress(to);\n        floorOracle = ICollectionFloorOracle(to);","title":"setFloorOracle and setDecisionParameters have no _noActive guard, so the owner can flip an allocated round from the ETH bid to keep (NFT to teamWallet)"},{"citation":"resolved","description":"From audit_flow #1. The only timing gate before paying is block.timestamp >= fallbackAt(id). onOracleResult rejects every delivery at or after fallbackAt (line 442), and the reference says deliveries arrive minutes later in a separate transaction. A request made in the last block or minutes before fallbackAt spends oraclePrice and can never be stored. requestDecision is permissionless, so a caller can trigger this for any round that nobody asked for earlier. It costs at most one paid ask per round, because the state then leaves Allocated. Fix: require fallbackAt(id) >= block.timestamp + minOracleLead before paying, and otherwise fall back or return.","line":404,"path":"src/KeelSpinVault.sol","reproduction":"Allocated round, stale floor, 100 IMD in the vault, pool window 24 h. At fallbackAt(id)-1, anyone calls requestDecision(id): the round goes to OraclePending and imdBalance drops by 0.5e18. One block later the Intake delivers a valid attestation and onOracleResult reverts StaleAnswer. settleFallback then resolves through the floor. Ran .imd/reads/proofs/Proof_bd065ea8a6d3.t.sol: FAIL 'IMD spent on an unanswerable request: 500000000000000000 != 0'.","severity":"low","snippet":"        if (r.oracleFailed || block.timestamp >= fallbackAt(id)) {","title":"requestDecision pays 0.5 IMD even when fallbackAt is too close for any answer to be accepted"},{"citation":"resolved","description":"From audit_flow #2. depositSaleProceeds is the only exit from AwaitingSale, and it requires nftForwarded. If the collection permanently refuses transfers out of the vault (paused or blacklisting ERC-721), _forward keeps catching and nftForwarded stays false. claim and withdraw then revert (round not Settled or Open), and settleFallback, executeDecision and requestDecision revert in _sync because activeRoundId was cleared. The round's remaining cash (unspent buffer plus refunds) is locked for good. The amount is small per round, and the vault otherwise keeps running.","line":613,"path":"src/KeelSpinVault.sol","reproduction":"Round kept through ObviousFloor (floor 30 ether vs backing 1 ether) while the collection's transfers are halted, so forward fails and nftForwarded == false, with rounds(id).cash == buffer > 0. forwardNFT(id) emits NFTForwardPending. depositSaleProceeds{value:1 ether}(id, h), alice.claim(id), withdraw(id) and settleFallback(id) all revert WrongState. The vault holds the buffer and nobody can reach it.","severity":"low","snippet":"        if (r.state != State.AwaitingSale || !r.nftForwarded) revert WrongState();","title":"A kept NFT that can never be forwarded leaves the round in AwaitingSale for good, with its buffer cash locked"},{"citation":"resolved","description":"From audit_flow #4. _reconcileOutcome sets activeRoundId = 0 before a round becomes AwaitingSale or Settled (line 534). When requestDecision, executeDecision or settleFallback is called afterwards, _sync reverts before the forward-retry or silent-return branches at lines 392-396, 475-479 and 487-491. Keepers retrying a forward through these functions get WrongState. forwardNFT still works, so no funds are affected.","line":321,"path":"src/KeelSpinVault.sol","reproduction":"Allocated round, floor.set(30 ether, now), requestDecision(id) moves it to AwaitingSale with activeRoundId == 0. A second requestDecision(id), executeDecision(id) or settleFallback(id) reverts WrongState instead of retrying _forward or returning.","severity":"info","snippet":"        if (activeRoundId != id || id == 0) revert WrongState();","title":"The AwaitingSale/Settled branches in requestDecision, executeDecision and settleFallback can never run, because _sync reverts once activeRoundId is cleared"},{"citation":"resolved","description":"From audit_economics #3. _settle(id, arrived) takes feeBps of everything unaccounted that is in the vault when the round settles, so a donation or forced ETH that lands during the round is taxed too. Bettors lose at most feeBps of the unsolicited amount; their principal is not touched.","line":537,"path":"src/KeelSpinVault.sol","reproduction":"feeBps 300, backing 1 ETH (bid 0.9 ETH). Send 1 ETH to the vault while the round is Allocated, then settleFallback with a 0.5 ETH floor (ETH path). Expected feeCredit(team) == 0.027 ETH. Actual: 0.057 ETH (3% of 1.9 ETH).","severity":"info","snippet":"        uint256 arrived = address(this).balance - accountedETH;","title":"The ETH-path fee is charged on all unaccounted ETH, including unsolicited transfers, not only on the depositor bid"}],"hash":"7311eaf1b558a03361123d3421f4f4bf8a5d35b6988d01f851e09a262362278a","nodeId":"f141be15-15f0-49d6-b60d-2c039f61704a","outcome":"completed","summary":"I've written the review to `.imd-findings.json`: 7 findings, none critical or high. I merged duplicates and reproduced every finding I kept against the code. All three proofs the specialists attached fail on this code for the reasons their findings give, and the first, second and fourth findings carry them. I added no source changes and removed the scratch copies afterwards.\n\n**Findings**\n1. **Medium – the oracle decision can never run on mainnet** (`src/KeelSpinVault.sol:358`). Three specialists reported this. FWAV2's live `settlementWindow()` is 3600 s, shorter than the brief's default `safetyMargin` of 2h. So `decisionDeadline` and `fallbackAt` both clamp silently to `allocatedAt`. The 0.5 IMD question is never asked, and `onOracleResult` and `executeDecision` always revert `StaleAnswer`. A token with no fresh floor is kept by `defaultKeep` with no valuation at all. This is a conflict between the brief's numbers and the live protocol, so the owner has to decide the scope: defaults that fit a 1-hour window, or rejecting settings that cannot work instead of clamping.\n2. **Medium – one unclaimed old bet blocks pool and router changes for good** (`:825` and `KeelFwaRouter.sol:57`). Two specialists reported this. Shares in rounds that are already Settled still count in `totalSharesOutstanding`. A single 0.001 ETH bet that is never claimed stops `setFwa`, `setRouter` and `router.setPool` permanently.\n3. **Medium – the owner can switch a live round from the ETH bid to keeping the NFT** (`:711`). `setFloorOracle` and `setDecisionParameters` lack the `_noActive` guard the other oracle setters have. On an allocated round, pointing the floor oracle at a contract reporting a high floor forces the \"obvious keep\" path, and the NFT goes to `teamWallet` instead of the 0.9 ETH bid going to the bettors. I traced this but did not write a test for it.\n4. **Low – IMD can be paid for a question that can't be answered in time** (`:404`). `requestDecision` pays 0.5 IMD even one block before `fallbackAt`, when no answer can still be accepted. This is limited to one paid ask per round.\n5. **Low – a stuck NFT locks the round's leftover ETH** (`:613`). If a kept NFT can never be forwarded (the collection blocks transfers), the round stays in AwaitingSale forever and its buffer can't be claimed.\n6. **Info – some retry branches can never run** (`:321`). The forward-retry and silent-return branches in `requestDecision`, `executeDecision` and `settleFallback` are unreachable, because `_sync` reverts once a round is resolved. `forwardNFT` still works, so no funds are affected.\n7. **Info – the fee is also taken on unsolicited ETH** (`:537`). On the ETH path, any ETH sent to the vault during the round is included in the fee base along with the bid.\n\nI didn't find anything the specialists missed. The Slither and Aderyn high leads (sending ETH to arbitrary addresses, weak randomness, reentrancy) don't hold: every send goes to the caller, a recipient they chose, or `teamWallet`; the \"randomness\" is the blackout clock check; and every entry point is `nonReentrant` and updates state before external calls. I could not confirm how a FWA request in the TimedOut state is cleared, because the reference doesn't document it.\n\nThe coverage record has a row for all 45 entry points (35 holds, 10 finding), plus rows for the \"tracked ETH never exceeds the vault balance\" invariant and `launch.json`. The manifest is valid: schema keys only, constructor arguments in the right order and count, and the router refers back to the vault.","treeHash":null,"usage":{"cachedInputTokens":921384,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":18158,"runtime":"claude","turns":13,"wallClockMs":250437}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8c9c4986ca881107","findings":[],"hash":"7397c5f4c22866115ecf95f81320266c9ca8b57fa2f250a15948d8cb3e5e6d45","nodeId":"f141be15-15f0-49d6-b60d-2c039f61704a","outcome":"completed","summary":"Review complete. Both findings from the previous round are fixed, and I found nothing new to report.\n\n**Settled findings**\n\n- **Stuck kept NFT after stranded settlement (medium):** fixed. `recoverStuckNFT` at `src/KeelSpinVault.sol:643` now accepts AwaitingSale or Settled-and-kept, mirroring `forwardNFT`. I ran the attached proof against the revised tree and it passes, where it failed with WrongState last round. An ETH-settled round is still refused.\n- **Zero lead pays for an unanswerable question (low):** fixed. The ask gate at line 432 now uses `>=`, agreeing with the callback cutoff at line 470. My fresh scratch test with lead zero at exactly `fallbackAt` shows no IMD spent and no intake request, while an ask one second earlier still reaches OraclePending and stores an answer.\n\n**Other checks**\n\n- The fix revision changed only those two lines. I read them as new code and traced the states they open; nothing reproducible came out of them.\n- Full suite: 141 tests pass, 0 fail, including the author's new tests for both scenarios.\n- The manifest in `launch.json` has only schema keys, and its argument lists match both constructors in count and order. Addresses match the brief and the oracle-consumer reference.\n\n**Output**\n\n`.imd-findings.json` holds an empty findings list, a settled record for both prior findings with the evidence above, and a coverage row for all 45 entry points plus the solvency invariant. I removed my scratch tests, so the tree is unchanged apart from the findings file.","treeHash":null,"usage":{"cachedInputTokens":1304600,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":13258,"runtime":"claude","turns":18,"wallClockMs":280112}},{"artifacts":[],"attempt":1,"bundleHash":"8fb8af663ced0c97fed37c40ca0221e8f10ee1e5ff9668ca3d1158d5e8fe5bcd","device":"d00f790fc692b1a4","findings":[],"hash":"84fb5a093d94004bfdeaa3e9f8043d1e3f060725e34e8634651cd3f0efcc5d33","nodeId":"1fc1ea14-b5d3-487e-9e2c-c4904db0ba05","outcome":"completed","summary":"Both defects I reported last round have been fixed in the implementation. I added tests that check the fixes, and the full suite passes: **140 tests across 12 suites, 0 failures, 0 skips**, run with the verifier's seed `0x742c…c513`.\n\n**What changed**\n- **`test/KeelRevisionChecks.t.sol`**: three new tests.\n  - `testFuzzPaidAskOnlyWhenAnswerCanLand`, 256 fuzz cases. It sets `minOracleLead` to random allowed values and calls `requestDecision` at random times from allocation up to `fallbackAt`. The vault must pay 0.5 IMD exactly when `now + lead < fallbackAt`. A paid ask must still be accepted when its answer arrives at `fallbackAt - 1`. Otherwise no IMD may be spent and the fallback must apply at once (keep by default). This is the boundary behind the old low-severity finding.\n  - `testRecoverAfterStrandedSettlementLeavesClaimsUnchanged`: a stuck kept NFT whose round was settled through the 30-day stranded path can now be recovered by anyone. The recovery moves only the NFT: claims, `accountedETH` and the round state stay the same, and a second proceeds deposit is still refused. This is the old medium-severity finding.\n  - `testRecoverOnNormallyForwardedSettledRoundReverts`: on a settled round where the NFT was delivered normally, the pool owes the vault nothing, so recovery still reverts and payouts are untouched.\n  - I also updated a stale comment that said the ask at exactly `fallbackAt` was \"reported separately\".\n- **`test/TEST_COVERAGE.md`**: replaced the \"Reported, not asserted\" section with one that marks both findings fixed and names the tests that check each fix, and updated the test counts.\n\n**Findings:** none this round. There is no `.imd-findings.json`, because both earlier defects are fixed and I found no new one.\n\n**Not covered:** the suite runs offline against mocks, so checks against live mainnet contracts (FWA's random draw and pricing, the marketplaces) are still owed, as before.\n\nOnly those two files under `test/` were changed, and `git status` shows nothing else.","treeHash":"ebce86a0973ed6c432ef19556dc6f8b6391bf9b0","usage":{"cachedInputTokens":659491,"inputTokens":24,"model":"claude-opus-5-5","outputTokens":9315,"runtime":"claude","turns":14,"wallClockMs":215370}},{"artifacts":[],"attempt":1,"bundleHash":"01317fbeb6112dffdc0c89920454a4f6c22ab8d85f4c44049e8cfa87522f8d92","device":"85f8921773f3e9b5","findings":[],"hash":"9b9eddd6d13327568fbbbedf557a6233581f5f1e26469ca99119335dce29fa70","nodeId":"ff02171a-f18a-4f87-85b7-f763c4899e07","outcome":"completed","summary":"Created [launch.json](/home/imdworker/.identitymd-nft1/work/84e3719a-76be-40ed-92fb-c792d6490dba/ff02171a-f18a-4f87-85b7-f763c4899e07/launch.json) with constructor arguments matching the compiled ABIs and the required deployment order.\n\n`forge build` passed. All 91 tests passed with the supplied fuzz seed. Only `launch.json` is changed for submission.","treeHash":"778c126d966f5d96cabd1417894c3cd50209d9fb","usage":{"cachedInputTokens":1079936,"inputTokens":64014,"model":"gpt-6-astra","outputTokens":6097,"runtime":"codex","turns":5,"wallClockMs":266214}},{"artifacts":[],"attempt":1,"bundleHash":"e186db3f63e620828f9727f77f1ad7885f32cf09651d02cb973f87714d5fd4a6","device":"09078b7cdfb673fe","findings":[{"description":"A round is marked kept when FWA could not deliver the NFT and named the vault as stuckNFTRecipient. nftStranded() then turns true after 30 days, and the team may call depositSaleProceeds to settle the round. After that, recoverStuckNFT(id) reverts WrongState because it requires AwaitingSale, and the vault is the only address FWA lets call recoverStuckNFT for that listing. forwardNFT accepts Settled+kept rounds, but _forward only emits NFTForwardPending because the vault never owns the NFT. The NFT stays in the FWA pool for good, although SECURITY.md says it 'remains forwardable'. Suggested fix: let recoverStuckNFT also run for Settled rounds with kept == true (or call pool.recoverStuckNFT from forwardNFT when stuckNFTRecipient == this).","line":637,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {MockFWA, MockNFT, MockIMD, MockFloor, MockIntake} from \"test/mocks/Protocols.sol\";\n\n/// A kept NFT that FWA could not deliver (stuckNFTRecipient == vault) and that the team settles through the\n/// 30-day stranded path can never be recovered afterwards: recoverStuckNFT requires AwaitingSale.\ncontract StrandedStuckProof is Test {\n    KeelSpinVault vault;\n    KeelFwaRouter router;\n    MockFWA pool;\n    MockNFT nft;\n    address alice = address(0xA11CE);\n    address team = address(0x7EA7);\n    address depositor = address(0xD3F0);\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        pool = new MockFWA();\n        nft = new MockNFT();\n        MockIMD imd = new MockIMD();\n        MockFloor floor = new MockFloor();\n        MockIntake intake = new MockIntake();\n        vault = new KeelSpinVault(\n            address(this), address(pool), address(floor), address(intake), address(imd), vm.addr(0xA77E57), team\n        );\n        router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        vm.deal(address(pool), 10000 ether);\n        vm.deal(alice, 10000 ether);\n        imd.mint(address(vault), 100 ether);\n    }\n\n    function testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() public {\n        uint256 id = vault.currentRoundId();\n        vm.prank(alice);\n        vault.bet{value: vault.liveTarget()}();\n        vault.spin(id);\n        uint256 listingId = pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether, depositor);\n        vault.syncRound(id);\n\n        // FWA's best-effort delivery fails: the NFT stays in the pool, stuck for the vault.\n        nft.setRejectTransfers(true);\n        vm.warp(block.timestamp + pool.settlementWindow());\n        pool.finalizeUnsettled(listingId);\n        vault.syncRound(id);\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.AwaitingSale));\n        assertEq(pool.stuckNFTRecipient(listingId), address(vault));\n\n        // 30 days later the team uses the documented stranded path to release the round's cash.\n        vm.warp(uint256(vault.rounds(id).allocatedAt) + 30 days);\n        assertTrue(vault.nftStranded(id));\n        vm.deal(team, 1 ether);\n        vm.prank(team);\n        vault.depositSaleProceeds{value: 1}(id, keccak256(\"stranded\"));\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.Settled));\n\n        // The collection becomes transferable again. The NFT must still be recoverable and forwardable\n        // (\"the NFT remains forwardable\"), but recoverStuckNFT reverts WrongState on a Settled round.\n        nft.setRejectTransfers(false);\n        vault.recoverStuckNFT(id);\n        vault.forwardNFT(id);\n        assertEq(nft.ownerOf(1), team, \"kept NFT is permanently stuck in the FWA pool\");\n    }\n\n    receive() external payable {}\n}","reproduction":"With MockFWA: bet to liveTarget, spin, allocate, then nft.setRejectTransfers(true). Warp past settlementWindow, call pool.finalizeUnsettled (the NFT is stuck, stuckNFTRecipient = vault) and vault.syncRound: the round is AwaitingSale, kept. Warp to allocatedAt + 30 days and have the team call depositSaleProceeds{value:1}: the round is Settled. Then nft.setRejectTransfers(false) and call vault.recoverStuckNFT(id). Expected: the NFT is recovered and forwarded to teamWallet. Actual: it reverts WrongState(), and forwardNFT leaves the NFT in the pool.","severity":"medium","title":"Stuck kept NFT becomes unrecoverable once the round is settled through the stranded path"},{"description":"requestDecision falls back only when block.timestamp + minOracleLead > fallbackAt(id). onOracleResult refuses every answer when block.timestamp >= fallbackAt(id). setDecisionParameters accepts oracleLead = 0. With that setting, a requestDecision at exactly fallbackAt pays 0.5 IMD for a question whose answer can never be accepted. The default (10 minutes) is not affected, so this is owner-configuration dependent. Fix: use >=, or require oracleLead > 0.","line":431,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {MockFWA, MockNFT, MockIMD, MockFloor, MockIntake} from \"test/mocks/Protocols.sol\";\n\ncontract ZeroLeadProof is Test {\n    function testNoPaidAskAtExactFallbackCutoff() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        MockFWA pool = new MockFWA();\n        MockNFT nft = new MockNFT();\n        MockIMD imd = new MockIMD();\n        MockIntake intake = new MockIntake();\n        KeelSpinVault vault = new KeelSpinVault(\n            address(this), address(pool), address(new MockFloor()), address(intake), address(imd), vm.addr(1), address(0x7EA7)\n        );\n        vault.setRouter(address(new KeelFwaRouter(address(this), address(vault), address(pool))));\n        vm.deal(address(pool), 100 ether);\n        imd.mint(address(vault), 10 ether);\n        vault.setDecisionParameters(1500, 500, 24 hours, 10 minutes, 20 minutes, 0, true);\n        uint256 id = vault.currentRoundId();\n        vault.bet{value: vault.liveTarget()}();\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, nft, 1, 1 ether, address(0xD3F0));\n        vault.syncRound(id);\n        vm.warp(vault.fallbackAt(id));\n        uint256 imdBefore = vault.imdBalance();\n        vault.requestDecision(id);\n        // onOracleResult refuses every answer at or after fallbackAt, so paying here only burns IMD.\n        assertEq(vault.imdBalance(), imdBefore, \"paid an oracle ask whose answer can never be accepted\");\n    }\n\n    receive() external payable {}\n}","reproduction":"Call setDecisionParameters(1500, 500, 24h, 10min, 20min, 0, true). Allocate a round with no floor, warp to fallbackAt(id) and call requestDecision(id). Expected: fallback with no IMD spent. Actual: 0.5 IMD is paid and the round goes to OraclePending, where any answer is refused with StaleAnswer.","severity":"low","title":"Paid oracle ask is still sent at exactly fallbackAt when minOracleLead is 0"}],"hash":"bbef090e124f989527190fb3b8095d1f266e3e4037e20813364600a69da5365a","nodeId":"1fc1ea14-b5d3-487e-9e2c-c4904db0ba05","outcome":"completed","summary":"I checked my tests against the implementer's latest revision. All 133 tests pass under the verifier's fuzz seed, I added tests for what changed, and I found two defects. Those are reported in `.imd-findings.json` rather than written as passing tests.\n\n**Defects reported** (each has a test that fails on the current code and should pass once it's fixed):\n1. **Medium: a kept NFT stuck inside FWA is lost for good after a \"stranded\" settlement.** Sometimes FWA can't deliver the NFT and leaves it in the pool for the vault to recover. If the team then uses the new 30-day stranded path to settle the round, `recoverStuckNFT` reverts with `WrongState` (`src/KeelSpinVault.sol:637`), because it only works while the round is awaiting sale. The vault is the only address FWA lets recover that NFT, and `forwardNFT` can't reach it, so it stays in the pool. That contradicts SECURITY.md, which says the NFT \"remains forwardable\".\n2. **Low: IMD can be wasted at the cutoff if the owner sets the minimum oracle lead to 0.** At exactly `fallbackAt`, `requestDecision` still pays 0.5 IMD for a question (line 431), but the callback refuses every answer from that moment on (line 469). The default lead of 10 minutes isn't affected.\n\n**What changed in the tests:**\n- **New `test/KeelRevisionChecks.t.sol`, 7 tests:**\n  - The new migration check (`unresolvedShares`): a refunded round that goes back to Open and a round mid-spin both block switching pools; withdrawals and claims release the stake correctly. One of these is a fuzz test with 1–60 bettors.\n  - A stuck NFT can still be recovered from the old pool after a migration, and sale proceeds can only be deposited once.\n  - The stranded-round release refuses callers who aren't the team or owner, a zero deposit and an empty sale hash, and a round settled in ETH never counts as stranded.\n  - The timing settings must stay strictly inside FWA's settlement window.\n  - With the oracle lead at zero, an answer arriving at the cutoff is refused and the round falls back.\n- **Lifecycle invariant (`test/KeelLifecycleInvariant.t.sol`):** it now also checks that `unresolvedShares` equals the stake of every round that isn't awaiting sale or settled, after random call sequences. It passes.\n- **`test/TEST_COVERAGE.md`:** describes the new file and lists the two reported defects.\n\nThe full run reports 2 failures; both are the proof tests in `test/scratch/`, which the verifier deletes. Without them: 133 passed, 0 failed.\n\nOne deviation from the brief I left alone: the brief asks for a 2h safety margin and a 1h fallback lead. The implementer changed these to 10 and 20 minutes because FWA's live settlement window is only one hour, and documented why.","treeHash":"acaf30328b98a3ff153dbf03d8fd439ce80bfc08","usage":{"cachedInputTokens":1974474,"inputTokens":46,"model":"claude-opus-5-5","outputTokens":23575,"runtime":"claude","turns":23,"wallClockMs":819483}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"cd8076510aad8b8e","findings":[{"citation":"resolved","description":"_noBets() (used by setFwa and setRouter) and KeelFwaRouter.setPool (src/KeelFwaRouter.sol:57) gate migration on totalSharesOutstanding == 0. totalSharesOutstanding only falls when a bettor withdraws or claims (_burn), so shares of a Settled round that a bettor never claims count forever, although a Settled round no longer depends on the pool or router. Any unprivileged actor can therefore freeze the owner-settable FWA/router wiring (a brief requirement, and FWA has already migrated V1 to V2 once) for the cost of one minimum bet (0.001 ETH) that they simply never claim back. The owner has no way to force a claim or exclude settled shares, so the vault can never follow a pool migration; the only remedy is a new deployment. Minimal fix: count only shares of rounds in Open/Spinning/Allocated/OraclePending/DecisionReady states (or track an openSharesOutstanding counter decremented at _settle/_state(AwaitingSale)), keeping the intended protection for funded, unresolved rounds.","line":825,"path":"src/KeelSpinVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeelSpinVault} from \"src/KeelSpinVault.sol\";\nimport {KeelFwaRouter} from \"src/KeelFwaRouter.sol\";\nimport {IFWAV2} from \"src/interfaces/IKeelProtocols.sol\";\n\n/// Minimal self-contained FWA stand-in: quote, acquire, one allocation, ETH-bid settlement.\ncontract TinyFWA is IFWAV2 {\n    uint256 public settlementDiscountBps = 9000;\n    uint256 public settlementWindow = 24 hours;\n    uint256 public nextRequest;\n    mapping(uint256 => Acquisition) internal _acq;\n    mapping(uint256 => Listing) internal _list;\n    mapping(address => uint256) public acquisitionRefundCredit;\n    mapping(uint256 => address) public stuckNFTRecipient;\n\n    function quoteAcquisitionPrice() external pure returns (uint256, uint256, uint256) {\n        return (0.1 ether, 0.003 ether, 0.103 ether);\n    }\n\n    function isPurchaseBlackout() external pure returns (bool) {\n        return false;\n    }\n\n    function acquire(address purchaser, uint256, uint256, uint256, uint256) external payable returns (uint256[] memory ids) {\n        ids = new uint256[](1);\n        ids[0] = ++nextRequest;\n        _acq[ids[0]] = Acquisition(purchaser, block.number, 0.1 ether, 0, 1);\n    }\n\n    function allocate(uint256 requestId, address collection, uint256 tokenId, uint256 backing) external {\n        Acquisition storage a = _acq[requestId];\n        _list[1] = Listing(collection, address(0xD3F0), a.purchaser, tokenId, 1, backing, 0, 0, 0, uint64(block.timestamp), 2);\n        a.listingId = 1;\n        a.status = 2;\n    }\n\n    function acquisitions(uint256 id) external view returns (Acquisition memory) {\n        return _acq[id];\n    }\n\n    function listings(uint256 id) external view returns (Listing memory) {\n        return _list[id];\n    }\n\n    function acceptDepositorBid(uint256 id) external {\n        Listing storage l = _list[id];\n        require(l.status == 2 && msg.sender == l.purchaser, \"not purchaser\");\n        l.status = 4;\n        (bool ok,) = payable(l.purchaser).call{value: l.value * settlementDiscountBps / 10000}(\"\");\n        require(ok, \"pay\");\n    }\n\n    function keepNFT(uint256) external pure {\n        revert(\"unused\");\n    }\n\n    function withdrawAcquisitionRefund() external pure returns (uint256) {\n        revert(\"unused\");\n    }\n\n    function recoverStuckNFT(uint256) external pure {\n        revert(\"unused\");\n    }\n\n    function rewards() external pure returns (address) {\n        return address(0);\n    }\n\n    function token() external pure returns (address) {\n        return address(0);\n    }\n\n    receive() external payable {}\n}\n\ncontract TinyCollection {\n    function ownerOf(uint256) external view returns (address) {\n        return address(this);\n    }\n}\n\ncontract TinyFloor {\n    function getFloor(address) external view returns (uint256, uint48) {\n        return (0.5 ether, uint48(block.timestamp));\n    }\n}\n\n/// A single never-claimed 0.001 ETH stake in a Settled round keeps totalSharesOutstanding != 0 forever,\n/// so setFwa / setRouter / router.setPool revert OutstandingBets although no Open or active round exists.\ncontract MigrationBlockedTest is Test {\n    KeelSpinVault vault;\n    KeelFwaRouter router;\n    TinyFWA pool;\n    TinyFloor floor;\n    TinyCollection collection;\n    address alice = address(0xA11CE);\n    address griefer = address(0x6B1E);\n    address team = address(0x7EA7);\n    address keeper = address(0xB07);\n\n    function setUp() public {\n        vm.chainId(1);\n        vm.warp(1800057600 + 1 hours);\n        vm.txGasPrice(10 gwei);\n        pool = new TinyFWA();\n        floor = new TinyFloor();\n        collection = new TinyCollection();\n        vault = new KeelSpinVault(\n            address(this), address(pool), address(floor), address(0x1A7A), address(0x1D), address(0xBEEF), team\n        );\n        router = new KeelFwaRouter(address(this), address(vault), address(pool));\n        vault.setRouter(address(router));\n        vm.deal(address(pool), 1000 ether);\n        vm.deal(alice, 100 ether);\n        vm.deal(griefer, 1 ether);\n        vm.deal(keeper, 1 ether);\n    }\n\n    function testSettledUnclaimedStakeMustNotBlockPoolMigration() public {\n        uint256 id = vault.currentRoundId();\n        // griefer enters first with the minimum bet, alice fills the rest of the round\n        vm.prank(griefer);\n        vault.bet{value: 0.001 ether}();\n        uint256 target = vault.liveTarget();\n        vm.prank(alice);\n        vault.bet{value: target - 0.001 ether}();\n        vm.prank(keeper);\n        vault.spin(id);\n        pool.allocate(vault.rounds(id).requestId, address(collection), 1, 1 ether);\n        vault.syncRound(id);\n        // floor 0.5 ETH haircut 0.425 < bid 0.9 * 1.05 -> ETH path at fallbackAt\n        vm.warp(vault.fallbackAt(id));\n        vault.settleFallback(id);\n        assertEq(uint256(vault.rounds(id).state), uint256(KeelSpinVault.State.Settled));\n        vm.prank(alice);\n        vault.claim(id);\n        // griefer never claims. No round is Open with shares, none is active.\n        assertEq(vault.activeRoundId(), 0);\n        assertEq(vault.rounds(vault.currentRoundId()).totalShares, 0);\n        assertGt(vault.stake(id, griefer), 0);\n\n        // Expected: the owner can still migrate the pool and router (nothing funded depends on them).\n        TinyFWA newPool = new TinyFWA();\n        KeelFwaRouter newRouter = new KeelFwaRouter(address(this), address(vault), address(newPool));\n        vault.setFwa(address(newPool));\n        vault.setRouter(address(newRouter));\n        assertEq(address(vault.fwa()), address(newPool));\n        // and the griefer's claim is unaffected\n        vm.prank(griefer);\n        vault.claim(id);\n    }\n}","reproduction":"1) griefer bet{0.001 ETH}() into round 1, alice fills the rest to liveTarget. 2) keeper spin(1); allocation synced; floor 0.5 ETH; settleFallback(1) takes the ETH bid, round 1 Settled. 3) alice claim(1); griefer never claims. State: activeRoundId == 0, current round has 0 shares, stake(1, griefer) == 0.001e18, totalSharesOutstanding == 0.001e18. 4) owner setFwa(newPool) -> expected success (no funded unresolved round exists); actual revert OutstandingBets(). Same for setRouter and router.setPool. See test/scratch/MigrationBlocked.t.sol (fails on current code with OutstandingBets).","severity":"medium","snippet":"        if (totalSharesOutstanding != 0 || activeRoundId != 0) revert OutstandingBets();","title":"One never-claimed Settled stake permanently blocks setFwa, setRouter and router.setPool"},{"citation":"resolved","description":"The supplied FWA reference reports settlementWindow() == 3600 s on mainnet. With the brief's defaults (safetyMargin 2 h, fallbackLead 1 h) decisionDeadline(id) saturates to allocatedAt and fallbackAt(id) == allocatedAt. Consequences on the live pool: requestDecision never pays the 0.5 IMD question (line 404 routes straight to _fallback), executeDecision can never run (line 480: block.timestamp >= decisionDeadline is always true), onOracleResult would reject any delivery (line 442), and a round with no fresh floor is kept by defaultKeep == true even when the ETH bid is the better value. The brief's central decision mechanism (oracle offer vs ethBid with marginBps) is dead code under the stated constructor defaults; the README documents the saturation but ships the incompatible defaults. The test suite only exercises a 24 h mock window for the oracle path. Fix: derive defaults from the live window (e.g. safetyMargin 10 min, fallbackLead 20 min for a 3600 s window) or make setDecisionParameters reject safetyMargin + fallbackLead >= fwa.settlementWindow() at first use, and document a required post-deploy setter call.","line":358,"path":"src/KeelSpinVault.sol","reproduction":"Mock pool.setWindow(3600); fund, spin, allocate and syncRound(id). Observed: decisionDeadline(id) == allocatedAt, fallbackAt(id) == allocatedAt. requestDecision(id) with no floor: IMD balance unchanged (no question paid), path == FallbackDefault, state AwaitingSale (NFT kept) although no valuation was ever obtained. executeDecision(id) can never execute a stored value. Reproduced in test/scratch/Probe.t.sol::testLiveWindowOracleUnreachable.","severity":"low","snippet":"        return uint256(r.allocatedAt) + (window > safetyMargin ? window - safetyMargin : 0);","title":"Default safetyMargin/fallbackLead exceed FWA's live 3600s settlementWindow, so the oracle path is unreachable and every round resolves by floor/defaultKeep at allocation"},{"citation":"resolved","description":"_reconcileOutcome computes arrived = balance - accountedETH and passes it as the fee base. arrived includes any unsolicited or forced ETH that reached the vault since the last reconciliation, so the team fee (up to 300 bps) is taken from donations and from any FWA refund credit pulled into the same round, contradicting the README statement that fees apply only to the ETH bid or sale proceeds. No bettor loses principal; the discrepancy is bounded by feeBps of the unsolicited amount.","line":541,"path":"src/KeelSpinVault.sol","reproduction":"feeBps = 300, backing 1 ETH (bid 0.9 ETH). Send 1 ETH to the vault while the round is Allocated, then settleFallback with a 0.5 ETH floor (ETH path). Expected fee credit 0.027 ETH (3% of 0.9); actual feeCredit(team) == 0.057 ETH (3% of 1.9). test/scratch/Probe.t.sol::testFeeOnDonation.","severity":"info","snippet":"        else _settle(id, arrived);","title":"ETH-path fee is charged on all unaccounted ETH, not only on the depositor bid"}],"hash":"e6ee6e300f17b09e9089435a7694c37e90a12b6a0621bd6ea0c78915a029aa06","nodeId":"203cf7b8-a0b6-4cfe-a607-ad1a32db1332","outcome":"completed","summary":"Review complete. `.imd-findings.json` is written at the repository root with three findings and 50 coverage rows, one per listed entry point plus five invariant and economic rows.\n\n**What I found**\n\n- **Medium. Migration setters can be frozen by one unclaimed stake.** `setFwa`, `setRouter` and the router's `setPool` all require the vault's total shares outstanding to be zero. Shares of a Settled round stay counted until that bettor claims. A griefer who bets the 0.001 ETH minimum and never claims blocks pool and router migration permanently, with no owner remedy. The guard at `src/KeelSpinVault.sol:825` is the root. A self-contained proof in `test/scratch/MigrationBlocked.t.sol` fails on the current code with `OutstandingBets()` and passes once only unresolved rounds are counted.\n- **Low. Default timing makes the oracle path dead on mainnet.** FWA's live settlement window is 3600 seconds. With the brief's 2 hour safety margin and 1 hour fallback lead, the deadline saturates to the allocation time, so every round takes the floor or defaultKeep fallback at once. The paid oracle question, `executeDecision`, and the oracle callback can never be used. The README documents the saturation, but the shipped defaults contradict the live pool. Reproduced with a 3600 second mock window.\n- **Info. ETH-path fee base includes unsolicited ETH.** The fee is applied to all unaccounted ETH, not just the depositor bid. With a 1 ETH donation and a 0.9 ETH bid at 300 bps, the team credit is 0.057 ETH rather than 0.027 ETH. No bettor principal is lost.\n\n**What held under the Economic Security, Invariant and Flow Gap checks**\n\nShare minting after refunds keeps losses with prior bettors and admits no inflation attack. The `accountedETH` conservation law holds across every writer. Spin debits before the purchase, credits only the real overpayment, and pays the keeper last. Withdraw and claim depend on no external contract. Refund griefing is bounded by FWA's own withdrawal lock during pending VRF. The owner has no path to bettor ETH. The existing 91 tests pass.\n\n**Not reached**\n\nI did not have the FWA source to confirm two leads, so they are not reported as findings: whether the real `acceptDepositorBid` payout is net of a settlement fee that `ethBid` ignores, and whether a partial refund credit on a fulfilled acquisition could be left in FWA until a later round sweeps it.","treeHash":null,"usage":{"cachedInputTokens":2696863,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":53311,"runtime":"claude","turns":33,"wallClockMs":998284}},{"artifacts":[],"attempt":1,"bundleHash":"fb5c18f3f1cd0e73d2f8426739d6884b7019cdeb93d516dfe08c151fe2ecd037","device":"dedc96e38388cb45","findings":[],"hash":"f37e44cdcb6e67bb7e90c88b83d1d8700d1575dd996724d63c0e14a67f8d7cba","nodeId":"ff02171a-f18a-4f87-85b7-f763c4899e07","outcome":"completed","summary":"The existing launch.json is correct against the accepted implementation and needs no change. The working tree is clean apart from my scratch test, which is deleted before submission.\n\n**What I verified**\n\n- **Manifest shape.** Exactly the keys kind, contracts and notes. Both contract names are valid identifiers under 32 characters and unique. Every constructor argument is a string. Notes are 648 characters.\n- **KeelSpinVault arguments** match the constructor's seven parameters in declaration order: owner, FWAV2, CollectionFloorOracle, IMD Intake v2, IMD token, oracle signer, teamWallet. Owner and teamWallet are `$owner`, as the brief says teamWallet defaults to the owner.\n- **KeelFwaRouter arguments** match its three parameters: owner as `$owner`, vault as `$contract:KeelSpinVault`, pool as the FWAV2 address. The reference points backward and fills an address.\n- **Oracle signer.** The live IMD oracle endpoint reports the attester as the same address the manifest carries, so it is a published value rather than a stand-in.\n- **Empty-chain deployment.** A scratch test deployed both contracts through a factory contract with the exact manifest values on chain id 1. Neither constructor calls another contract. Owners, teamWallet, dependency addresses and signer read back correctly, both runtimes fit under the EIP-170 size limit, and the owner's follow-up setRouter call succeeds.\n- **Notes accuracy.** Betting reverts until the owner sets the router, and builder revenue claims go to the vault's teamWallet through the FWA transfer helper the owner must configure, as the notes state.\n\n**Checks**\n\n| Check | Result |\n|---|---|\n| forge build | ok |\n| forge test with the verifier's fuzz seed | 107 passed, 0 failed |\n\nNo file other than launch.json is part of the deliverable, and launch.json itself is unchanged from the accepted version because it already matches the accepted source and the brief.","treeHash":"bbd1286252f3858c9d9c8bff7665e3fe9658f180","usage":{"cachedInputTokens":481553,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":6279,"runtime":"claude","turns":11,"wallClockMs":168633}}],"verification":[{"checks":[{"durationMs":35497,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 35.41s\nCompiler run successful!\n","passed":true},{"durationMs":16692,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 3.50ms (2.18ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638295)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 11.47ms (9.46ms CPU time)\n\nRan 20 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615743)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833361)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113802)\n[PASS] testFinalizerResolvesFirst() (gas: 1597638)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1880934)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966714)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1831989)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191378)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511234)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266641)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152595)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660062)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 13.56ms (46.77ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17835348)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573624)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 2.19s (178.78ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 30858501, ~: 20031095)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 31562444, ~: 21028343)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 2.29s (4.55s CPU time)\n\nRan 33 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860342)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555118)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843692)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790096)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644672)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987447)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121433)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926351)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849337)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847577)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577822)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535599)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596546)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393511)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094849)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2490555)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094246)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886752)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690013)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573298)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985705)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985955)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907593)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543943)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960677)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081048)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385125)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587046)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571761)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146011)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 2.29s (164.69ms CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1621)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 22      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 447     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 405     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 346     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 401     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000005171\n  Bound result 8713686547752044\n  Bound result 299000000000002825\n  Bound result 120000000000012289\n  Bound result 4979\n  Bound result 299000000000000208\n  Bound result 299000000000005736\n  Bound result 120000000000008167\n  Bound result 22230\n  Bound result 120000000000001096\n  Bound result 5387\n  Bound result 82754044108979615\n  Bound result 120000000000002105\n  Bound result 6790\n  Bound result 120000000000007115\n  Bound result 11019\n  Bound result 66669652837796516\n  Bound result 15151\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.60s (16.60s CPU time)\n\nRan 7 test suites in 16.60s (23.40s CPU time): 102 tests passed, 0 failed, 0 skipped (102 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"launch.json\":26,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":890,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":168,\"test/KeelOracle.t.sol\":414,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":308,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"145fa241ba5d48056c0d0e72b781649cae9b83a89ed5c1688846be32c2c2b581","verifiedTreeHash":"fa95bb6aadf82c87622ce68a84471a7cea83b3b5","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":36689,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 36.60s\nCompiler run successful!\n","passed":true},{"durationMs":16430,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638298)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.60ms (3.10ms CPU time)\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 11.27ms (3.89ms CPU time)\n\nRan 35 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860345)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555121)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843717)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790085)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644719)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987494)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121436)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926376)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849340)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847602)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577844)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535646)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596593)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393514)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094852)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2748155)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094249)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886802)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690033)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573301)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985752)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985958)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907596)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543968)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960680)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081092)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385169)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587071)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571808)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146014)\n[PASS] testZeroLeadNeverPaysForAnUnanswerableQuestion() (gas: 1668710)\n[PASS] testZeroLeadStillAsksOneSecondBeforeFallback() (gas: 2768117)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 14.28ms (151.57ms CPU time)\n\nRan 22 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615787)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833364)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113805)\n[PASS] testFinalizerResolvesFirst() (gas: 1597660)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1881048)\n[PASS] testRecoverStuckNFTRejectsEthSettledRound() (gas: 1677581)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966758)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1832033)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191381)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511278)\n[PASS] testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() (gas: 2271140)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266672)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152639)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660106)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 14.31ms (69.58ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17876771)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573630)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 2.16s (163.18ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 29610324, ~: 18395242)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 30348085, ~: 19436844)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 2.16s (4.24s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1616)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 14      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 454     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 408     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 348     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 392     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002687\n  Bound result 3669\n  Bound result 299000000000002869\n  Bound result 120000000000012170\n  Bound result 6116\n  Bound result 299000000000000319\n  Bound result 299000000000005841\n  Bound result 120000000000008201\n  Bound result 4172\n  Bound result 120000000000000730\n  Bound result 5624\n  Bound result 82754044108979615\n  Bound result 120000000000001150\n  Bound result 6948\n  Bound result 120000000000007161\n  Bound result 1049126309\n  Bound result 66669652837796506\n  Bound result 15217\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.34s (16.34s CPU time)\n\nRan 7 test suites in 16.34s (20.71s CPU time): 106 tests passed, 0 failed, 0 skipped (106 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":893,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":168,\"test/KeelOracle.t.sol\":445,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":357,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6664,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:885: KeelSpinVault._send(address,uint256) (src/KeelSpinVault.sol#885-889) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelFwaRouter.sol:174: KeelFwaRouter._payTeam(uint256) (src/KeelFwaRouter.sol#174-179) sends eth to arbitrary user\n[high/medium] weak-prng at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) uses a weak PRNG: \"block.timestamp % 43200 >= 39600 + 2700 || fwa.isPurchaseBlackout() (src/KeelSpinVault.sol#318)\"\n[high/medium] reentrancy-balance at src/KeelFwaRouter.sol:92: Reentrancy in KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:279: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#279-282) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:339: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:621: Reentrancy in KeelSpinVault._forward(uint256) (src/KeelSpinVault.sol#621-636):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:500: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#500-510):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:542: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#542-551):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:512: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#512-528):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:339: Reentrancy in KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:512: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#512-528):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:500: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#500-510):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:542: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#542-551):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:344: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:640: Reentrancy in KeelSpinVault.recoverStuckNFT(uint256) (src/KeelSpinVault.sol#640-647):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459):\n[medium/medium] unused-return at src/KeelSpinVault.sol:234: KeelSpinVault.liveTarget() (src/KeelSpinVault.sol#234-238) ignores return value by (None,None,total) = fwa.quoteAcquisitionPrice() (src/KeelSpinVault.sol#235)\n[medium/medium] unused-return at src/KeelFwaRouter.sol:163: KeelFwaRouter.recoverBuilderAllowanceAsETH() (src/KeelFwaRouter.sol#163-168) ignores return value by IFwaRewards(pool.rewards()).withdrawTokenBuyAllowanceAsETH() (src/KeelFwaRouter.sol#165)\n[medium/medium] unused-return at src/KeelSpinVault.sol:856: KeelSpinVault.setDefinition(uint256,string) (src/KeelSpinVault.sol#856-861) ignores return value by OracleBody.escape(value) (src/KeelSpinVault.sol#858)\n[medium/medium] unused-return at src/KeelSpinVault.sol:344: KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378) ignores return value by pool.withdrawAcquisitionRefund() (src/KeelSpinVault.sol#355)\n[low/medium] missing-zero-check at src/KeelSpinVault.sol:203: KeelSpinVault.constructor(address,address,address,address,address,address,address).teamWallet_ (src/KeelSpinVault.sol#203) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:344: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378):\n[low/medium] reentrancy-benign at src/KeelFwaRouter.sol:129: Reentrancy in KeelFwaRouter._queue(address,uint256,uint256) (src/KeelFwaRouter.sol#129-154):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:542: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#542-551):\n[low/medium] timestamp at src/KeelSpinVault.sol:415: KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-459) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:616: KeelSpinVault.nftStranded(uint256) (src/KeelSpinVault.sol#616-619) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:339: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:534: KeelSpinVault._fallback(uint256) (src/KeelSpinVault.sol#534-540) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:92: KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:400: KeelSpinVault.freshFloor(address) (src/KeelSpinVault.sol#400-407) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:279: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#279-282) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:512: KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#512-528) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:387: KeelSpinVault.fallbackAt(uint256) (src/KeelSpinVault.sol#387-391) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:249: KeelSpinVault._bet(uint256) (src/KeelSpinVault.sol#249-277) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:649: KeelSpinVault.depositSaleProceeds(uint256,bytes32) (src/KeelSpinVault.sol#649-657) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:530: KeelSpinVault._validValue(KeelSpinVault.Round) (src/KeelSpinVault.sol#530-532) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:591: KeelSpinVault.onERC721Received(address,address,uint256,bytes) (src/KeelSpinVault.sol#591-607) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:112: KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#112-120) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:500: KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#500-510) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:462: KeelSpinVault.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/KeelSpinVault.sol#462-486) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":783,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/OracleBody.sol:30: `abi.encodePacked()` Hash Collision\n[high] reentrancy-state-change at src/KeelFwaRouter.sol:57: Reentrancy: State change after external call (8 places)\n[high] weak-randomness at src/KeelSpinVault.sol:318: Weak Randomness\n[low] centralization-risk at src/KeelFwaRouter.sol:18: Centralization Risk (23 places)\n[low] costly-loop at src/KeelSpinVault.sol:256: Costly operations inside loop\n[low] internal-function-used-once at src/OracleAttestation.sol:200: Internal Function Used Only Once\n[low] large-numeric-literal at src/KeelSpinVault.sol:109: Large Numeric Literal\n[low] literal-instead-of-constant at src/KeelSpinVault.sol:352: Literal Instead of Constant (13 places)\n[low] non-reentrant-not-first at src/KeelFwaRouter.sol:55: `nonReentrant` is Not the First Modifier (9 places)\n[low] require-revert-in-loop at src/KeelSpinVault.sol:256: Loop Contains `require`/`revert` (2 places)\n[low] state-no-address-check at src/KeelSpinVault.sol:210: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/KeelFwaRouter.sol:165: Unchecked Return (4 places)\n[low] uninitialized-local-variable at src/KeelSpinVault.sol:256: Uninitialized Local Variable (2 places)\n[low] unused-public-function at src/KeelSpinVault.sol:669: Public Function Not Used Internally","passed":true},{"durationMs":8623,"exitCode":0,"name":"proof d5f4aa5ee643","output":"Compiling 60 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.97s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-0af4ee25/Proof_d5f4aa5ee643.t.sol:StrandedStuckProof\n[PASS] testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() (gas: 1870252)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.01ms (972.63µs CPU time)\n\nRan 1 test suite in 2.79ms (2.01ms 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":"37b9049d55d0aeef38c846534a1532438fc0eba9ba1449343d2ca025033626b2","verifiedTreeHash":"617e816cc064469e3e45c859580a809d1b65b90d","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":41073,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 40.95s\nCompiler run successful!\n","passed":true},{"durationMs":18085,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 862717)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 443517)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.91ms (6.77ms CPU time)\n\nRan 27 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2830690)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2528608)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2815842)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2764053)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2617724)\n[PASS] testDecisionReadsDiscountLive() (gas: 2956893)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1551723)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3090393)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2896342)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2820095)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2818260)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1539240)\n[PASS] testFloorFailureIsUnavailable() (gas: 2509358)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2570417)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1367992)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3065753)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2857290)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2547093)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1599059)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2955130)\n[PASS] testOracleAtMarginTakesEth() (gas: 2955426)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2877352)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2517913)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2931061)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1558094)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2545789)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3107127)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 16.20ms (136.47ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17321080)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 307884)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 303506)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4517645)\n[PASS] testParameterBounds() (gas: 191269)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320117)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2116274)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 769162)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1855381)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1267916)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367033)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 156.23ms (172.81ms CPU time)\n\nRan 17 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1589194)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2803809)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1538221)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1663304)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3083381)\n[PASS] testFinalizerResolvesFirst() (gas: 1571090)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1853741)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1935009)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 1988255)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1803404)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3160463)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1892714)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2056653)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1648509)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3235699)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152527)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1633497)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 1.98s (61.15ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13189)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70226)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2611435)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.22s (7.88ms CPU time)\n\nRan 23 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 55082362)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 540823)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 783311)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1774316)\n[PASS] testCarryOverAndWithdraw() (gas: 532278)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 326334)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2346864)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 26690719, ~: 17347063)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 28246005, ~: 18893887)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 312746)\n[PASS] testHugeBetAtomicBound() (gas: 3245846)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 397686)\n[PASS] testMultipleRoundCarryOver() (gas: 687645)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 637317)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2023569)\n[PASS] testPauseOnlyStopsBets() (gas: 1037026)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 951406)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 368301)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1000723)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 315561)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1184880)\n[PASS] testRewardClampsToTwoPercent() (gas: 673581)\n[PASS] testSpinRewardAndRouterGuards() (gas: 856116)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 2.22s (4.34s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1638)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 22      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 452     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 408     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 352     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 404     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002643\n  Bound result 3582\n  Bound result 299000000000002821\n  Bound result 32279339271334782\n  Bound result 13725\n  Bound result 299000003096268768\n  Bound result 299000000000005955\n  Bound result 120000000000009098\n  Bound result 17819\n  Bound result 120000000000003055\n  Bound result 5934\n  Bound result 82754044108979615\n  Bound result 120000000000000844\n  Bound result 7001\n  Bound result 120000000033540521\n  Bound result 6046\n  Bound result 66963257433764003\n  Bound result 17397\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 17.97s (17.96s CPU time)\n\nRan 7 test suites in 17.97s (24.57s CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":132,\"docs/SECURITY.md\":39,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":19,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":854,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":163,\"test/KeelOracle.t.sol\":328,\"test/KeelRounds.t.sol\":343,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":252,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":7662,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:846: KeelSpinVault._send(address,uint256) (src/KeelSpinVault.sol#846-850) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelFwaRouter.sol:174: KeelFwaRouter._payTeam(uint256) (src/KeelFwaRouter.sol#174-179) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:287: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312) sends eth to arbitrary user\n[high/medium] weak-prng at src/KeelSpinVault.sol:287: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312) uses a weak PRNG: \"block.timestamp % 43200 >= 39600 + 2700 || fwa.isPurchaseBlackout() (src/KeelSpinVault.sol#294)\"\n[high/medium] reentrancy-balance at src/KeelFwaRouter.sol:92: Reentrancy in KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:287: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:287: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312):\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:255: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#255-258) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:315: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#315-318) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:389: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:389: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:320: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#320-352):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:472: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#472-482):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:514: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#514-523):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:389: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:389: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:389: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:601: Reentrancy in KeelSpinVault.recoverStuckNFT(uint256) (src/KeelSpinVault.sol#601-608):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:315: Reentrancy in KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#315-318):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:484: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#484-500):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:584: Reentrancy in KeelSpinVault._forward(uint256) (src/KeelSpinVault.sol#584-599):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:472: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#472-482):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:484: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#484-500):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:514: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#514-523):\n[medium/medium] unused-return at src/KeelFwaRouter.sol:163: KeelFwaRouter.recoverBuilderAllowanceAsETH() (src/KeelFwaRouter.sol#163-168) ignores return value by IFwaRewards(pool.rewards()).withdrawTokenBuyAllowanceAsETH() (src/KeelFwaRouter.sol#165)\n[medium/medium] unused-return at src/KeelSpinVault.sol:211: KeelSpinVault.liveTarget() (src/KeelSpinVault.sol#211-215) ignores return value by (None,None,total) = fwa.quoteAcquisitionPrice() (src/KeelSpinVault.sol#212)\n[medium/medium] unused-return at src/KeelSpinVault.sol:320: KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#320-352) ignores return value by pool.withdrawAcquisitionRefund() (src/KeelSpinVault.sol#329)\n[medium/medium] unused-return at src/KeelSpinVault.sol:817: KeelSpinVault.setDefinition(uint256,string) (src/KeelSpinVault.sol#817-822) ignores return value by OracleBody.escape(value) (src/KeelSpinVault.sol#819)\n[low/medium] missing-zero-check at src/KeelSpinVault.sol:180: KeelSpinVault.constructor(address,address,address,address,address,address,address).teamWallet_ (src/KeelSpinVault.sol#180) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:287: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312):\n[low/medium] reentrancy-benign at src/KeelFwaRouter.sol:129: Reentrancy in KeelFwaRouter._queue(address,uint256,uint256) (src/KeelFwaRouter.sol#129-154):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:514: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#514-523):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:320: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#320-352):\n[low/medium] timestamp at src/KeelSpinVault.sol:374: KeelSpinVault.freshFloor(address) (src/KeelSpinVault.sol#374-381) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:472: KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#472-482) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:92: KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:484: KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#484-500) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:287: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#287-312) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:434: KeelSpinVault.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/KeelSpinVault.sol#434-458) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:361: KeelSpinVault.fallbackAt(uint256) (src/KeelSpinVault.sol#361-365) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:506: KeelSpinVault._fallback(uint256) (src/KeelSpinVault.sol#506-512) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:226: KeelSpinVault._bet(uint256) (src/KeelSpinVault.sol#226-253) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:112: KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#112-120) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:561: KeelSpinVault.onERC721Received(address,address,uint256,bytes) (src/KeelSpinVault.sol#561-577) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:315: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#315-318) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:255: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#255-258) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:502: KeelSpinVault._validValue(KeelSpinVault.Round) (src/KeelSpinVault.sol#502-504) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:389: KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#389-431) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":660,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/OracleBody.sol:30: `abi.encodePacked()` Hash Collision\n[high] reentrancy-state-change at src/KeelFwaRouter.sol:57: Reentrancy: State change after external call (7 places)\n[high] weak-randomness at src/KeelSpinVault.sol:294: Weak Randomness\n[low] centralization-risk at src/KeelFwaRouter.sol:18: Centralization Risk (23 places)\n[low] costly-loop at src/KeelSpinVault.sol:233: Costly operations inside loop\n[low] internal-function-used-once at src/OracleAttestation.sol:200: Internal Function Used Only Once\n[low] large-numeric-literal at src/KeelSpinVault.sol:102: Large Numeric Literal\n[low] literal-instead-of-constant at src/KeelSpinVault.sol:326: Literal Instead of Constant (12 places)\n[low] non-reentrant-not-first at src/KeelFwaRouter.sol:55: `nonReentrant` is Not the First Modifier (9 places)\n[low] require-revert-in-loop at src/KeelSpinVault.sol:233: Loop Contains `require`/`revert` (2 places)\n[low] state-no-address-check at src/KeelSpinVault.sol:187: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/KeelFwaRouter.sol:165: Unchecked Return (4 places)\n[low] uninitialized-local-variable at src/KeelSpinVault.sol:233: Uninitialized Local Variable (2 places)\n[low] unused-public-function at src/KeelSpinVault.sol:630: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3af45e5fd441d8d7426b910232729fdbc91b8d94b3470ec453ec29f95a3d43d5","verifiedTreeHash":"1c5ad648e3d19cec17675c337acc2a5a9d65a360","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":56507,"exitCode":0,"name":"build","output":"Compiling 71 files with Solc 0.8.26\nSolc 0.8.26 finished in 56.41s\nCompiler run successful!\n","passed":true},{"durationMs":23771,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 862717)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 443517)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 5.73ms (4.60ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13189)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70226)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2611435)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 12.06ms (3.83ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17321080)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 307884)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 303506)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4517645)\n[PASS] testParameterBounds() (gas: 191269)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320117)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2116274)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 769162)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1855381)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1267916)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367033)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 172.97ms (185.31ms CPU time)\n\nRan 17 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1589194)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2803809)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1538221)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1663304)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3083381)\n[PASS] testFinalizerResolvesFirst() (gas: 1571090)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1853741)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1935009)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 1988255)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1803404)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3160463)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1892714)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2056653)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1648509)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3235699)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152527)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1633497)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 372.81ms (55.11ms CPU time)\n\nRan 16 tests for test/KeelFailurePaths.t.sol:KeelFailurePathsTest\n[PASS] testAllSaleEventFieldsIdentifyCorrectRoundAndTransaction() (gas: 1561208)\n[PASS] testAttestationExpiryIsInclusiveThenExecutionExpiresOneSecondLater() (gas: 2954062)\n[PASS] testDefinitionLengthEdgesAndControlCharacters() (gas: 789801)\n[PASS] testERC1271SignerCallbackFitsStipendAndRejectsWrongKey() (gas: 2996522)\n[PASS] testExactClockSkewToleranceIsAccepted() (gas: 2825404)\n[PASS] testExecutionAtExactAnswerExpiryAndMaximumOffer() (gas: 2937381)\n[PASS] testFuzzDefinitionJsonRoundTripAndExactNftQuestion(bytes32,uint16) (runs: 128, μ: 1740941, ~: 1590037)\nLogs:\n  Bound result 117\n\n[PASS] testFuzzMalformedAcquisitionResponseRevertsAtomically(uint8) (runs: 32, μ: 919272, ~: 927359)\nLogs:\n  Bound result 0\n\n[PASS] testFuzzSignedMalformedAttestationLeavesPendingRequestIntact(uint8) (runs: 128, μ: 3053874, ~: 3052015)\nLogs:\n  Bound result 0\n\n[PASS] testGasExhaustingNftRecipientCannotUndoKeepAndCanRetry() (gas: 2223705)\n[PASS] testLowImdThresholdEventAfterExactPaymentAndStoredValueEvent() (gas: 2663427)\n[PASS] testNonCanonicalHighSSignatureDoesNotConsumeRequest() (gas: 2656330)\n[PASS] testOracleFailureCallbackRejectsUnknownAndConsumedIdsWithoutMovingAssets() (gas: 2944196)\n[PASS] testRejectingFeeRecipientPreservesCreditAndCannotBlockBettorClaims() (gas: 1875153)\n[PASS] testRejectingWithdrawalRecipientPreservesSharesAndCanRetryElsewhere() (gas: 568774)\n[PASS] testZeroFeeAndInconsistentTotalRevertBeforeAnySpend() (gas: 859675)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 491.99ms (717.05ms CPU time)\n\nRan 27 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2830690)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2528608)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2815842)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2764053)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2617724)\n[PASS] testDecisionReadsDiscountLive() (gas: 2956893)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1551723)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3090393)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2896342)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2820095)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2818260)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1539240)\n[PASS] testFloorFailureIsUnavailable() (gas: 2509358)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2570417)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1367992)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3065753)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2857290)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2547093)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1599059)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2955130)\n[PASS] testOracleAtMarginTakesEth() (gas: 2955426)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2877352)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2517913)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2931061)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1558094)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2545789)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3107127)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 2.61s (126.82ms CPU time)\n\nRan 23 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 55082362)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 540823)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 783311)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1774316)\n[PASS] testCarryOverAndWithdraw() (gas: 532278)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 326334)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2346864)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 26674655, ~: 14089846)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 28229925, ~: 15633754)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 312746)\n[PASS] testHugeBetAtomicBound() (gas: 3245846)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 397686)\n[PASS] testMultipleRoundCarryOver() (gas: 687645)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 637317)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2023569)\n[PASS] testPauseOnlyStopsBets() (gas: 1037026)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 951406)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 368301)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1000723)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 315561)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1184880)\n[PASS] testRewardClampsToTwoPercent() (gas: 673581)\n[PASS] testSpinRewardAndRouterGuards() (gas: 856116)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 2.76s (5.45s CPU time)\n\nRan 5 tests for test/KeelAccountingProperties.t.sol:KeelAccountingPropertiesTest\n[PASS] testCapacityIsReleasedAndExistingBettorCanTopUpAt500() (gas: 55385753)\n[PASS] testConcurrentSaleRefundAndOpenRoundBalancesStayIsolated() (gas: 2804989)\n[PASS] testFuzzRefundedShareRoundTripsCannotProfit(uint96,uint8) (runs: 512, μ: 6369045, ~: 4110789)\nLogs:\n  Bound result 2746\n  Bound result 5\n\n[PASS] testFuzzUpTo500BettorsRefundExitRefillAndShuffledClaims(uint16,uint256,uint16,uint16) (runs: 128, μ: 31199984, ~: 20547236)\nLogs:\n  Bound result 14\n  Bound result 5\n\n[PASS] testTeamRotationCannotRedirectAlreadyAccruedFeeCredits() (gas: 3258438)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.03s (3.65s CPU time)\n\nRan 1 test for test/KeelBuilderInvariant.t.sol:KeelBuilderInvariantTest\n[PASS]\nKeelBuilderInvariantTest invariants:\n[PASS] invariantBuilderAssetsStayWithRouterHelperOrTeam\n[PASS] invariantBuilderOperationsCannotConsumeBettorEth\n[PASS] invariantEverySuccessfulQueueConsumesExactlyOneScopedPermit\n KeelBuilderInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| BuilderHandler | advanceBlock    | 786   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimFromHelper | 752   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimTokens     | 718   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | credit          | 712   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | donateETH       | 735   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | enableHelper    | 764   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | mintHeld        | 774   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | queueHeld       | 763   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | recoverETH      | 724   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | spinAndRefund   | 713   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | withdrawETH     | 751   | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 57366309911980636\n  Bound result 32719032206016321727\n  Bound result 34626421890482177\n  Bound result 9531915015125499503\n  Bound result 3104\n  Bound result 28\n  Bound result 8865\n  Bound result 301\n  Bound result 1\n  Bound result 9999999999999601\n  Bound result 499\n  Bound result 27925494101429287909\n  Bound result 8420157447478343072\n  Bound result 85197318335300737\n  Bound result 100000000000000000\n  Bound result 9000000000000000\n  Bound result 19295979712995\n  Bound result 79099938193396609\n  Bound result 2442\n  Bound result 47426032271538023218\n  Bound result 9999999999999998\n  Bound result 15239\n  Bound result 785\n  Bound result 2910000000000000000\n  Bound result 20000000000000000\n  Bound result 7000000000000000000\n  Bound result 24576\n  Bound result 343\n  Bound result 696\n  Bound result 90055064477554320026\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.16s (4.15s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1634)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 25      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 450     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 406     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 345     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 408     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002643\n  Bound result 3582\n  Bound result 299000000000002821\n  Bound result 32279339271334782\n  Bound result 13725\n  Bound result 299000003096268768\n  Bound result 299000000000005955\n  Bound result 120000000000008603\n  Bound result 1695533112\n  Bound result 120000000000003055\n  Bound result 5934\n  Bound result 82754044108979615\n  Bound result 120000000000000844\n  Bound result 7001\n  Bound result 120000000033540521\n  Bound result 6046\n  Bound result 100939815503787617\n  Bound result 17397\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.88s (15.87s CPU time)\n\nRan 2 tests for test/KeelLifecycleInvariant.t.sol:KeelLifecycleInvariantTest\n[PASS]\nKeelLifecycleInvariantTest invariants:\n[PASS] invariantImdOnlyPaysOwnRequests\n[PASS] invariantIndependentEthFlowLedger\n[PASS] invariantLiabilitiesSharesAndTerminalStates\n KeelLifecycleInvariantTest invariants (runs: 128, calls: 12288, reverts: 0)\n\n╭------------------+-----------------+-------+---------+----------╮\n| Contract         | Selector        | Calls | Reverts | Discards |\n+=================================================================+\n| LifecycleHandler | badResult       | 794   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | cashOut         | 780   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | changeMarket    | 785   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | claimFees       | 773   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | decide          | 728   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | deposit         | 786   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | donate          | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | elapse          | 769   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | externalResolve | 772   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fulfill         | 748   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fundAndSpin     | 1501  | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | request         | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | result          | 801   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | sell            | 776   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | setPolicy       | 775   | 0       | 0        |\n╰------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 20000\n  Bound result 95\n  Bound result 37666202615568730\n  Bound result 3782\n  Bound result 14944806920267234\n  Bound result 13\n  Bound result 289999999999999997\n  Bound result 2199\n  Bound result 21060\n  Bound result 1024\n  Bound result 32881\n  Bound result 270000000000019845\n  Bound result 10000000000000000\n  Bound result 25077620939162480\n  Bound result 7933\n  Bound result 50000\n  Bound result 49115\n  Bound result 16418\n  Bound result 0\n  Bound result 6656\n  Bound result 28\n  Bound result 1275\n  Bound result 270000001080644451\n  Bound result 14216\n  Bound result 242\n  Bound result 6292\n  Bound result 17910\n  Bound result 262\n  Bound result 6326\n  Bound result 90000\n  Bound result 270000003464927844\n  Bound result 200000\n  Bound result 511\n  Bound result 3697\n  Bound result 242\n  Bound result 570000007276808073\n  Bound result 270000000000021119\n  Bound result 1099511627776\n  Bound result 301\n  Bound result 9992\n  Bound result 58089233741591132\n  Bound result 50000000000\n  Bound result 44945212059388835\n  Bound result 6974\n  Bound result 257\n  Bound result 270000003100810051\n  Bound result 4999999999999907\n  Bound result 5000000000000000\n  Bound result 450\n  Bound result 50\n  Bound result 3432033553368324272\n  Bound result 16021\n  Bound result 15\n  Bound result 270000000000000302\n  Bound result 15643\n  Bound result 2999999999965559\n  Bound result 5688\n  Bound result 270000000000001001\n  Bound result 3704\n  Bound result 256\n  Bound result 5972\n  Bound result 61455\n\n[PASS] testLifecycleHandlerExercisesOracleRefundFallbackAndExternalResolution() (gas: 12433148)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 86400\n  Bound result 123\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 23.67s (23.69s CPU time)\n\nRan 11 test suites in 23.67s (53.17s CPU time): 115 tests passed, 0 failed, 0 skipped (115 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":132,\"docs/SECURITY.md\":39,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":19,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":854,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelAccountingProperties.t.sol\":201,\"test/KeelBase.t.sol\":121,\"test/KeelBuilderInvariant.t.sol\":302,\"test/KeelFailurePaths.t.sol\":369,\"test/KeelInvariant.t.sol\":163,\"test/KeelLifecycleInvariant.t.sol\":489,\"test/KeelOracle.t.sol\":328,\"test/KeelRounds.t.sol\":343,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":252,\"test/OracleConformance.t.sol\":95,\"test/TEST_COVERAGE.md\":27,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5992161059ab592bd998d2fd9b700944b2f37a25c5a76991ede6a8be320d8a4b","verifiedTreeHash":"aa243ad585d4724a01ebb48e2b8c2aab1b824599","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":35633,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 35.54s\nCompiler run successful!\n","passed":true},{"durationMs":16376,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 3.06ms (1.78ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638295)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.59ms (7.60ms CPU time)\n\nRan 33 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860342)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555118)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843692)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790096)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644672)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987447)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121433)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926351)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849337)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847577)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577822)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535599)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596546)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393511)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094849)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2490555)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094246)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886752)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690013)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573298)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985705)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985955)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907593)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543943)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960677)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081048)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385125)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587046)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571761)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146011)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 19.27ms (146.75ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17835348)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573624)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 166.38ms (187.17ms CPU time)\n\nRan 20 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615743)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833361)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113802)\n[PASS] testFinalizerResolvesFirst() (gas: 1597638)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1880934)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966714)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1831989)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191378)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511234)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266641)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152595)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660062)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 2.24s (52.57ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 30858501, ~: 20031095)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 31562444, ~: 21028343)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 2.33s (4.64s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1624)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 21      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 447     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 406     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 346     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 404     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000005171\n  Bound result 8713686547752044\n  Bound result 299000000000002825\n  Bound result 120000000000012289\n  Bound result 4979\n  Bound result 299000000000000208\n  Bound result 299000000000005736\n  Bound result 120000000000008167\n  Bound result 22230\n  Bound result 120000000000001096\n  Bound result 5387\n  Bound result 82754044108979615\n  Bound result 120000000000002105\n  Bound result 6790\n  Bound result 120000000000007115\n  Bound result 11019\n  Bound result 108979339827269753\n  Bound result 15151\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.27s (16.26s CPU time)\n\nRan 7 test suites in 16.27s (21.05s CPU time): 102 tests passed, 0 failed, 0 skipped (102 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":890,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":168,\"test/KeelOracle.t.sol\":414,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":308,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6430,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelSpinVault.sol:882: KeelSpinVault._send(address,uint256) (src/KeelSpinVault.sol#882-886) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/KeelFwaRouter.sol:174: KeelFwaRouter._payTeam(uint256) (src/KeelFwaRouter.sol#174-179) sends eth to arbitrary user\n[high/medium] weak-prng at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) uses a weak PRNG: \"block.timestamp % 43200 >= 39600 + 2700 || fwa.isPurchaseBlackout() (src/KeelSpinVault.sol#318)\"\n[high/medium] reentrancy-balance at src/KeelFwaRouter.sol:92: Reentrancy in KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[high/medium] reentrancy-eth at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:279: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#279-282) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/KeelSpinVault.sol:339: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:620: Reentrancy in KeelSpinVault._forward(uint256) (src/KeelSpinVault.sol#620-635):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:499: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#499-509):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:511: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#511-527):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:541: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#541-550):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:511: Reentrancy in KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#511-527):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:339: Reentrancy in KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:415: Reentrancy in KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:637: Reentrancy in KeelSpinVault.recoverStuckNFT(uint256) (src/KeelSpinVault.sol#637-644):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:499: Reentrancy in KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#499-509):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:541: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#541-550):\n[medium/medium] reentrancy-no-eth at src/KeelSpinVault.sol:344: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378):\n[medium/medium] unused-return at src/KeelSpinVault.sol:234: KeelSpinVault.liveTarget() (src/KeelSpinVault.sol#234-238) ignores return value by (None,None,total) = fwa.quoteAcquisitionPrice() (src/KeelSpinVault.sol#235)\n[medium/medium] unused-return at src/KeelFwaRouter.sol:163: KeelFwaRouter.recoverBuilderAllowanceAsETH() (src/KeelFwaRouter.sol#163-168) ignores return value by IFwaRewards(pool.rewards()).withdrawTokenBuyAllowanceAsETH() (src/KeelFwaRouter.sol#165)\n[medium/medium] unused-return at src/KeelSpinVault.sol:344: KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378) ignores return value by pool.withdrawAcquisitionRefund() (src/KeelSpinVault.sol#355)\n[medium/medium] unused-return at src/KeelSpinVault.sol:853: KeelSpinVault.setDefinition(uint256,string) (src/KeelSpinVault.sol#853-858) ignores return value by OracleBody.escape(value) (src/KeelSpinVault.sol#855)\n[low/medium] missing-zero-check at src/KeelSpinVault.sol:203: KeelSpinVault.constructor(address,address,address,address,address,address,address).teamWallet_ (src/KeelSpinVault.sol#203) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:311: Reentrancy in KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336):\n[low/medium] reentrancy-benign at src/KeelFwaRouter.sol:129: Reentrancy in KeelFwaRouter._queue(address,uint256,uint256) (src/KeelFwaRouter.sol#129-154):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:344: Reentrancy in KeelSpinVault._sync(uint256) (src/KeelSpinVault.sol#344-378):\n[low/medium] reentrancy-benign at src/KeelSpinVault.sol:541: Reentrancy in KeelSpinVault._resolve(uint256,bool,KeelSpinVault.DecisionPath) (src/KeelSpinVault.sol#541-550):\n[low/medium] timestamp at src/KeelSpinVault.sol:529: KeelSpinVault._validValue(KeelSpinVault.Round) (src/KeelSpinVault.sol#529-531) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:511: KeelSpinVault.settleFallback(uint256) (src/KeelSpinVault.sol#511-527) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:311: KeelSpinVault.spin(uint256) (src/KeelSpinVault.sol#311-336) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:461: KeelSpinVault.onOracleResult(bytes32,OracleAttestation.Attestation,bytes) (src/KeelSpinVault.sol#461-485) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:339: KeelSpinVault.syncRound(uint256) (src/KeelSpinVault.sol#339-342) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:92: KeelFwaRouter.claimBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#92-110) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:400: KeelSpinVault.freshFloor(address) (src/KeelSpinVault.sol#400-407) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:615: KeelSpinVault.nftStranded(uint256) (src/KeelSpinVault.sol#615-618) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:646: KeelSpinVault.depositSaleProceeds(uint256,bytes32) (src/KeelSpinVault.sol#646-654) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:279: KeelSpinVault._nextDepositRound(uint256) (src/KeelSpinVault.sol#279-282) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:499: KeelSpinVault.executeDecision(uint256) (src/KeelSpinVault.sol#499-509) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:387: KeelSpinVault.fallbackAt(uint256) (src/KeelSpinVault.sol#387-391) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:249: KeelSpinVault._bet(uint256) (src/KeelSpinVault.sol#249-277) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelFwaRouter.sol:112: KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256) (src/KeelFwaRouter.sol#112-120) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:590: KeelSpinVault.onERC721Received(address,address,uint256,bytes) (src/KeelSpinVault.sol#590-606) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:415: KeelSpinVault.requestDecision(uint256) (src/KeelSpinVault.sol#415-458) uses timestamp for comparisons\n[low/medium] timestamp at src/KeelSpinVault.sol:533: KeelSpinVault._fallback(uint256) (src/KeelSpinVault.sol#533-539) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":697,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/OracleBody.sol:30: `abi.encodePacked()` Hash Collision\n[high] reentrancy-state-change at src/KeelFwaRouter.sol:57: Reentrancy: State change after external call (8 places)\n[high] weak-randomness at src/KeelSpinVault.sol:318: Weak Randomness\n[low] centralization-risk at src/KeelFwaRouter.sol:18: Centralization Risk (23 places)\n[low] costly-loop at src/KeelSpinVault.sol:256: Costly operations inside loop\n[low] internal-function-used-once at src/OracleAttestation.sol:200: Internal Function Used Only Once\n[low] large-numeric-literal at src/KeelSpinVault.sol:109: Large Numeric Literal\n[low] literal-instead-of-constant at src/KeelSpinVault.sol:352: Literal Instead of Constant (13 places)\n[low] non-reentrant-not-first at src/KeelFwaRouter.sol:55: `nonReentrant` is Not the First Modifier (9 places)\n[low] require-revert-in-loop at src/KeelSpinVault.sol:256: Loop Contains `require`/`revert` (2 places)\n[low] state-no-address-check at src/KeelSpinVault.sol:210: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/KeelFwaRouter.sol:165: Unchecked Return (4 places)\n[low] uninitialized-local-variable at src/KeelSpinVault.sol:256: Uninitialized Local Variable (2 places)\n[low] unused-public-function at src/KeelSpinVault.sol:666: Public Function Not Used Internally","passed":true},{"durationMs":8812,"exitCode":0,"name":"proof 8165f5b9cc52","output":"Compiling 60 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.14s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-b813c4f1/Proof_8165f5b9cc52.t.sol:DeadlineCollapseTest\n[PASS] test_oraclePathReachableWithLiveOneHourWindow() (gas: 4080494)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.03ms (2.86ms CPU time)\n\nRan 1 test suite in 3.71ms (3.03ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":9117,"exitCode":0,"name":"proof 635de21bff9e","output":"2026-10-18T13:15:31.493316Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-O6U69F/repo/test/imd-proof-b813c4f1/Proof_8165f5b9cc52.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.52s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-a0be46ed/Proof_635de21bff9e.t.sol:MigrationBlockedTest\n[PASS] testSettledUnclaimedStakeMustNotBlockPoolMigration() (gas: 4055928)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.82ms (1.00ms CPU time)\n\nRan 1 test suite in 3.55ms (1.82ms 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":"6e61cdf13d63c5e194966570d7bba1f48e7c39a19cf9bde8bd5609efd3579cbc","verifiedTreeHash":"7125894dd6c8c22604473049cdb672de1d767904","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":72225,"exitCode":0,"name":"build","output":"Compiling 72 files with Solc 0.8.26\nSolc 0.8.26 finished in 72.02s\nCompiler run successful!\n","passed":true},{"durationMs":30495,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 7.57ms (5.56ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638298)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 10.15ms (7.21ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17876771)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573630)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 198.76ms (220.97ms CPU time)\n\nRan 16 tests for test/KeelFailurePaths.t.sol:KeelFailurePathsTest\n[PASS] testAllSaleEventFieldsIdentifyCorrectRoundAndTransaction() (gas: 1587470)\n[PASS] testAttestationExpiryIsInclusiveThenExecutionExpiresOneSecondLater() (gas: 2984699)\n[PASS] testDefinitionLengthEdgesAndControlCharacters() (gas: 790511)\n[PASS] testERC1271SignerCallbackFitsStipendAndRejectsWrongKey() (gas: 3024671)\n[PASS] testExactClockSkewToleranceIsAccepted() (gas: 2855826)\n[PASS] testExecutionAtExactAnswerExpiryAndMaximumOffer() (gas: 2968520)\n[PASS] testFuzzDefinitionJsonRoundTripAndExactNftQuestion(bytes32,uint16) (runs: 128, μ: 1783305, ~: 1634440)\nLogs:\n  Bound result 13\n\n[PASS] testFuzzMalformedAcquisitionResponseRevertsAtomically(uint8) (runs: 32, μ: 942850, ~: 950331)\nLogs:\n  Bound result 0\n\n[PASS] testFuzzSignedMalformedAttestationLeavesPendingRequestIntact(uint8) (runs: 128, μ: 3085852, ~: 3083621)\nLogs:\n  Bound result 2\n\n[PASS] testGasExhaustingNftRecipientCannotUndoKeepAndCanRetry() (gas: 2251132)\n[PASS] testLowImdThresholdEventAfterExactPaymentAndStoredValueEvent() (gas: 2690569)\n[PASS] testNonCanonicalHighSSignatureDoesNotConsumeRequest() (gas: 2683791)\n[PASS] testOracleFailureCallbackRejectsUnknownAndConsumedIdsWithoutMovingAssets() (gas: 2974323)\n[PASS] testRejectingFeeRecipientPreservesCreditAndCannotBlockBettorClaims() (gas: 1901851)\n[PASS] testRejectingWithdrawalRecipientPreservesSharesAndCanRetryElsewhere() (gas: 601746)\n[PASS] testZeroFeeAndInconsistentTotalRevertBeforeAnySpend() (gas: 882197)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 329.54ms (772.68ms CPU time)\n\nRan 35 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860345)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555121)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843717)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790085)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644719)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987494)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121436)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926376)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849340)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847602)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577844)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535646)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596593)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393514)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094852)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2748155)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094249)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886802)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690033)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573301)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985752)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985958)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907596)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543968)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960680)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081092)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385169)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587071)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571808)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146014)\n[PASS] testZeroLeadNeverPaysForAnUnanswerableQuestion() (gas: 1668710)\n[PASS] testZeroLeadStillAsksOneSecondBeforeFallback() (gas: 2768117)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 650.35ms (151.18ms CPU time)\n\nRan 10 tests for test/KeelRevisionChecks.t.sol:KeelRevisionChecksTest\n[PASS] testDecisionTimingMustStayStrictlyInsideWindow() (gas: 208318)\n[PASS] testFuzzPaidAskOnlyWhenAnswerCanLand(uint256,uint256) (runs: 256, μ: 2372550, ~: 2867238)\nLogs:\n  Bound result 16820\n  Bound result 1800145797\n\n[PASS] testFuzzUnresolvedSharesTracksNonTerminalStake(uint256,uint8) (runs: 64, μ: 13891399, ~: 7924598)\nLogs:\n  Bound result 12\n  Bound result 59164267104551792\n  Bound result 65438810702162871\n  Bound result 101974451182126303\n  Bound result 25866786742067136\n  Bound result 5489949377854895\n  Bound result 29805579148312206\n  Bound result 6170147123414837\n  Bound result 5493614698995024\n  Bound result 105347895020770692\n  Bound result 46570612738336064\n  Bound result 8848020117579140\n  Bound result 88329509238840431\n\n[PASS] testRecoverAfterStrandedSettlementLeavesClaimsUnchanged() (gas: 2053635)\n[PASS] testRecoverOnNormallyForwardedSettledRoundReverts() (gas: 1771269)\n[PASS] testRefundedRoundStakeStaysUnresolvedAndBlocksMigration() (gas: 2689263)\n[PASS] testSpinningRoundBlocksMigrationEvenWithNoOtherStake() (gas: 2269708)\n[PASS] testStrandedReleaseRequiresOperatorAndNonZeroDeposit() (gas: 3438108)\n[PASS] testStuckNftInOldPoolRecoverableAfterMigration() (gas: 3551422)\n[PASS] testZeroOracleLeadAnswerAtCutoffIsRefusedAndFallsBack() (gas: 2981524)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 1.37s (1.80s CPU time)\n\nRan 22 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615787)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833364)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113805)\n[PASS] testFinalizerResolvesFirst() (gas: 1597660)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1881048)\n[PASS] testRecoverStuckNFTRejectsEthSettledRound() (gas: 1677581)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966758)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1832033)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191381)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511278)\n[PASS] testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() (gas: 2271140)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266672)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152639)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660106)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 3.11s (77.73ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 29601793, ~: 16395915)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 30339785, ~: 17491679)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 3.11s (6.18s CPU time)\n\nRan 5 tests for test/KeelAccountingProperties.t.sol:KeelAccountingPropertiesTest\n[PASS] testCapacityIsReleasedAndExistingBettorCanTopUpAt500() (gas: 57963408)\n[PASS] testConcurrentSaleRefundAndOpenRoundBalancesStayIsolated() (gas: 2868109)\n[PASS] testFuzzRefundedShareRoundTripsCannotProfit(uint96,uint8) (runs: 512, μ: 6623836, ~: 3990954)\nLogs:\n  Bound result 21727\n  Bound result 50\n\n[PASS] testFuzzUpTo500BettorsRefundExitRefillAndShuffledClaims(uint16,uint256,uint16,uint16) (runs: 128, μ: 31730018, ~: 18652070)\nLogs:\n  Bound result 14\n  Bound result 5\n\n[PASS] testTeamRotationCannotRedirectAlreadyAccruedFeeCredits() (gas: 3311034)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.35s (4.17s CPU time)\n\nRan 1 test for test/KeelBuilderInvariant.t.sol:KeelBuilderInvariantTest\n[PASS]\nKeelBuilderInvariantTest invariants:\n[PASS] invariantBuilderAssetsStayWithRouterHelperOrTeam\n[PASS] invariantBuilderOperationsCannotConsumeBettorEth\n[PASS] invariantEverySuccessfulQueueConsumesExactlyOneScopedPermit\n KeelBuilderInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| BuilderHandler | advanceBlock    | 786   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimFromHelper | 752   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimTokens     | 718   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | credit          | 712   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | donateETH       | 735   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | enableHelper    | 764   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | mintHeld        | 774   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | queueHeld       | 763   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | recoverETH      | 724   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | spinAndRefund   | 713   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | withdrawETH     | 751   | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 57366309911980636\n  Bound result 32719032206016321727\n  Bound result 71996739901653383\n  Bound result 9531915015125499503\n  Bound result 2662730016\n  Bound result 1500000\n  Bound result 9202\n  Bound result 13\n  Bound result 1\n  Bound result 2678400\n  Bound result 20000000000000000\n  Bound result 27925494101429287909\n  Bound result 8420157447478343072\n  Bound result 4000000000000001\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 19295979712995\n  Bound result 79099938193396609\n  Bound result 2905\n  Bound result 47426032271538023218\n  Bound result 9999999999999998\n  Bound result 15109\n  Bound result 797\n  Bound result 90000000000000000\n  Bound result 10000000000000000\n  Bound result 450\n  Bound result 24576\n  Bound result 344\n  Bound result 2070\n  Bound result 80235064477554320475\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.16s (5.15s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1626)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 13      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 449     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 405     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 348     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 411     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002687\n  Bound result 3669\n  Bound result 299000000000002869\n  Bound result 120000000000012169\n  Bound result 2883353711672747\n  Bound result 299000000000000319\n  Bound result 299000000000005841\n  Bound result 120000000000008203\n  Bound result 4171\n  Bound result 30000000000000000\n  Bound result 5946\n  Bound result 82754044108979615\n  Bound result 120000000000000399\n  Bound result 6965\n  Bound result 120000000000007242\n  Bound result 1138031329\n  Bound result 115793144302164720\n  Bound result 15245\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 20.96s (20.94s CPU time)\n\nRan 2 tests for test/KeelLifecycleInvariant.t.sol:KeelLifecycleInvariantTest\n[PASS]\nKeelLifecycleInvariantTest invariants:\n[PASS] invariantImdOnlyPaysOwnRequests\n[PASS] invariantIndependentEthFlowLedger\n[PASS] invariantLiabilitiesSharesAndTerminalStates\n KeelLifecycleInvariantTest invariants (runs: 128, calls: 12288, reverts: 0)\n\n╭------------------+-----------------+-------+---------+----------╮\n| Contract         | Selector        | Calls | Reverts | Discards |\n+=================================================================+\n| LifecycleHandler | badResult       | 794   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | cashOut         | 780   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | changeMarket    | 785   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | claimFees       | 773   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | decide          | 728   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | deposit         | 786   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | donate          | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | elapse          | 769   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | externalResolve | 772   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fulfill         | 748   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fundAndSpin     | 1501  | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | request         | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | result          | 801   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | sell            | 776   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | setPolicy       | 775   | 0       | 0        |\n╰------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 20000\n  Bound result 57680\n  Bound result 37666202615568730\n  Bound result 3973\n  Bound result 1\n  Bound result 14944806920267234\n  Bound result 299\n  Bound result 279999999999999994\n  Bound result 797\n  Bound result 22173\n  Bound result 15\n  Bound result 32880\n  Bound result 270000001557516853\n  Bound result 9\n  Bound result 2984931311726363\n  Bound result 7933\n  Bound result 20000\n  Bound result 49115\n  Bound result 902\n  Bound result 13\n  Bound result 3669829402\n  Bound result 117\n  Bound result 983\n  Bound result 270000001080644453\n  Bound result 14609\n  Bound result 242\n  Bound result 7149\n  Bound result 17818\n  Bound result 64\n  Bound result 32423\n  Bound result 2232\n  Bound result 11\n  Bound result 270000000000004998\n  Bound result 3600\n  Bound result 1024\n  Bound result 7460\n  Bound result 242\n  Bound result 570000000000057655\n  Bound result 270001099511627776\n  Bound result 21528\n  Bound result 13\n  Bound result 7429\n  Bound result 58089233741591132\n  Bound result 100000000000000000\n  Bound result 83019655932542976\n  Bound result 6077\n  Bound result 257\n  Bound result 270000000000003934\n  Bound result 1800\n  Bound result 27000000000000000\n  Bound result 500\n  Bound result 140\n  Bound result 3432033553368324272\n  Bound result 16121\n  Bound result 0\n  Bound result 270000000000000014\n  Bound result 16044\n  Bound result 2999999999999778\n  Bound result 6656\n  Bound result 270000000000000009\n  Bound result 3752989702\n  Bound result 1815085818\n  Bound result 9112\n  Bound result 3600\n\n[PASS] testLifecycleHandlerExercisesOracleRefundFallbackAndExternalResolution() (gas: 12596449)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 86400\n  Bound result 123\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 30.36s (30.38s CPU time)\n\nRan 12 test suites in 30.36s (68.60s CPU time): 140 tests passed, 0 failed, 0 skipped (140 total tests)\n","passed":true},{"durationMs":113,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":893,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelAccountingProperties.t.sol\":201,\"test/KeelBase.t.sol\":121,\"test/KeelBuilderInvariant.t.sol\":302,\"test/KeelFailurePaths.t.sol\":369,\"test/KeelInvariant.t.sol\":168,\"test/KeelLifecycleInvariant.t.sol\":494,\"test/KeelOracle.t.sol\":445,\"test/KeelRevisionChecks.t.sol\":270,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":357,\"test/OracleConformance.t.sol\":95,\"test/TEST_COVERAGE.md\":37,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"84fb5a093d94004bfdeaa3e9f8043d1e3f060725e34e8634651cd3f0efcc5d33","verifiedTreeHash":"ebce86a0973ed6c432ef19556dc6f8b6391bf9b0","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":32831,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 32.75s\nCompiler run successful!\n","passed":true},{"durationMs":16800,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13189)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70226)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2611435)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 5.27ms (3.66ms CPU time)\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 862717)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 443517)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 11.76ms (10.63ms CPU time)\n\nRan 17 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1589194)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2803809)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1538221)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1663304)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3083381)\n[PASS] testFinalizerResolvesFirst() (gas: 1571090)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1853741)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1935009)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 1988255)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1803404)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3160463)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1892714)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2056653)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1648509)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3235699)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152527)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1633497)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 12.42ms (52.98ms CPU time)\n\nRan 27 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2830690)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2528608)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2815842)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2764053)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2617724)\n[PASS] testDecisionReadsDiscountLive() (gas: 2956893)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1551723)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3090393)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2896342)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2820095)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2818260)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1539240)\n[PASS] testFloorFailureIsUnavailable() (gas: 2509358)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2570417)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1367992)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3065753)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2857290)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2547093)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1599059)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2955130)\n[PASS] testOracleAtMarginTakesEth() (gas: 2955426)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2877352)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2517913)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2931061)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1558094)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2545789)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3107127)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 2.11s (123.19ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17321080)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 307884)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 303506)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4517645)\n[PASS] testParameterBounds() (gas: 191269)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320117)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2116274)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 769162)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1855381)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1267916)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367033)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 2.11s (200.62ms CPU time)\n\nRan 23 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 55082362)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 540823)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 783311)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1774316)\n[PASS] testCarryOverAndWithdraw() (gas: 532278)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 326334)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2346864)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 26690719, ~: 17347063)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 28246005, ~: 18893887)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 312746)\n[PASS] testHugeBetAtomicBound() (gas: 3245846)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 397686)\n[PASS] testMultipleRoundCarryOver() (gas: 687645)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 637317)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2023569)\n[PASS] testPauseOnlyStopsBets() (gas: 1037026)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 951406)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 368301)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1000723)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 315561)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1184880)\n[PASS] testRewardClampsToTwoPercent() (gas: 673581)\n[PASS] testSpinRewardAndRouterGuards() (gas: 856116)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 2.11s (4.18s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1635)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 22      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 450     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 407     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 352     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 404     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002643\n  Bound result 3582\n  Bound result 299000000000002821\n  Bound result 32279339271334782\n  Bound result 13725\n  Bound result 299000003096268768\n  Bound result 299000000000005955\n  Bound result 120000000000009098\n  Bound result 17819\n  Bound result 120000000000003055\n  Bound result 5934\n  Bound result 82754044108979615\n  Bound result 120000000000000844\n  Bound result 7001\n  Bound result 120000000033540521\n  Bound result 6046\n  Bound result 146597993491075395\n  Bound result 17397\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.71s (16.70s CPU time)\n\nRan 7 test suites in 16.71s (23.07s CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":65,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":132,\"docs/SECURITY.md\":39,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":19,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"launch.json\":26,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":854,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":163,\"test/KeelOracle.t.sol\":328,\"test/KeelRounds.t.sol\":343,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":252,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9b9eddd6d13327568fbbbedf557a6233581f5f1e26469ca99119335dce29fa70","verifiedTreeHash":"778c126d966f5d96cabd1417894c3cd50209d9fb","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":58731,"exitCode":0,"name":"build","output":"Compiling 72 files with Solc 0.8.26\nSolc 0.8.26 finished in 58.63s\nCompiler run successful!\n","passed":true},{"durationMs":26024,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 3.58ms (2.31ms CPU time)\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638295)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 9.82ms (7.70ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17835348)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573624)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 523.75ms (261.93ms CPU time)\n\nRan 33 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860342)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555118)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843692)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790096)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644672)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987447)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121433)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926351)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849337)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847577)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577822)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535599)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596546)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393511)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094849)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2490555)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094246)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886752)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690013)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573298)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985705)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985955)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907593)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543943)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960677)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081048)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385125)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587046)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571761)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146011)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 2.79s (152.09ms CPU time)\n\nRan 16 tests for test/KeelFailurePaths.t.sol:KeelFailurePathsTest\n[PASS] testAllSaleEventFieldsIdentifyCorrectRoundAndTransaction() (gas: 1587470)\n[PASS] testAttestationExpiryIsInclusiveThenExecutionExpiresOneSecondLater() (gas: 2984696)\n[PASS] testDefinitionLengthEdgesAndControlCharacters() (gas: 790511)\n[PASS] testERC1271SignerCallbackFitsStipendAndRejectsWrongKey() (gas: 3024668)\n[PASS] testExactClockSkewToleranceIsAccepted() (gas: 2855823)\n[PASS] testExecutionAtExactAnswerExpiryAndMaximumOffer() (gas: 2968517)\n[PASS] testFuzzDefinitionJsonRoundTripAndExactNftQuestion(bytes32,uint16) (runs: 128, μ: 1794505, ~: 1658871)\nLogs:\n  Bound result 13\n\n[PASS] testFuzzMalformedAcquisitionResponseRevertsAtomically(uint8) (runs: 32, μ: 942213, ~: 950331)\nLogs:\n  Bound result 0\n\n[PASS] testFuzzSignedMalformedAttestationLeavesPendingRequestIntact(uint8) (runs: 128, μ: 3085623, ~: 3083567)\nLogs:\n  Bound result 2\n\n[PASS] testGasExhaustingNftRecipientCannotUndoKeepAndCanRetry() (gas: 2251132)\n[PASS] testLowImdThresholdEventAfterExactPaymentAndStoredValueEvent() (gas: 2690566)\n[PASS] testNonCanonicalHighSSignatureDoesNotConsumeRequest() (gas: 2683788)\n[PASS] testOracleFailureCallbackRejectsUnknownAndConsumedIdsWithoutMovingAssets() (gas: 2974320)\n[PASS] testRejectingFeeRecipientPreservesCreditAndCannotBlockBettorClaims() (gas: 1901851)\n[PASS] testRejectingWithdrawalRecipientPreservesSharesAndCanRetryElsewhere() (gas: 601746)\n[PASS] testZeroFeeAndInconsistentTotalRevertBeforeAnySpend() (gas: 882197)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 3.04s (706.66ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 30849971, ~: 20031095)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 31554144, ~: 21028343)\nLogs:\n  Bound result 300\n  Bound result 11836\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 3.04s (5.94s CPU time)\n\nRan 7 tests for test/KeelRevisionChecks.t.sol:KeelRevisionChecksTest\n[PASS] testDecisionTimingMustStayStrictlyInsideWindow() (gas: 208274)\n[PASS] testFuzzUnresolvedSharesTracksNonTerminalStake(uint256,uint8) (runs: 64, μ: 14433458, ~: 8008185)\nLogs:\n  Bound result 12\n  Bound result 59164267104551792\n  Bound result 65438810702162871\n  Bound result 101974451182126303\n  Bound result 25866786742067136\n  Bound result 5489949377854895\n  Bound result 29805579148312206\n  Bound result 6170147123414837\n  Bound result 5493614698995024\n  Bound result 105347895020770692\n  Bound result 46570612738336064\n  Bound result 8848020117579140\n  Bound result 88329509238840431\n\n[PASS] testRefundedRoundStakeStaysUnresolvedAndBlocksMigration() (gas: 2689242)\n[PASS] testSpinningRoundBlocksMigrationEvenWithNoOtherStake() (gas: 2269686)\n[PASS] testStrandedReleaseRequiresOperatorAndNonZeroDeposit() (gas: 3438054)\n[PASS] testStuckNftInOldPoolRecoverableAfterMigration() (gas: 3551372)\n[PASS] testZeroOracleLeadAnswerAtCutoffIsRefusedAndFallsBack() (gas: 2981424)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 3.14s (1.00s CPU time)\n\nRan 5 tests for test/KeelAccountingProperties.t.sol:KeelAccountingPropertiesTest\n[PASS] testCapacityIsReleasedAndExistingBettorCanTopUpAt500() (gas: 57963408)\n[PASS] testConcurrentSaleRefundAndOpenRoundBalancesStayIsolated() (gas: 2868109)\n[PASS] testFuzzRefundedShareRoundTripsCannotProfit(uint96,uint8) (runs: 512, μ: 6815982, ~: 4537405)\nLogs:\n  Bound result 15397\n  Bound result 11\n\n[PASS] testFuzzUpTo500BettorsRefundExitRefillAndShuffledClaims(uint16,uint256,uint16,uint16) (runs: 128, μ: 31994584, ~: 21864144)\nLogs:\n  Bound result 14\n  Bound result 5\n\n[PASS] testTeamRotationCannotRedirectAlreadyAccruedFeeCredits() (gas: 3311034)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.14s (3.80s CPU time)\n\nRan 20 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615743)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833361)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113802)\n[PASS] testFinalizerResolvesFirst() (gas: 1597638)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1880934)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966714)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1831989)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191378)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511234)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266641)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152595)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660062)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 3.15s (57.76ms CPU time)\n\nRan 1 test for test/KeelBuilderInvariant.t.sol:KeelBuilderInvariantTest\n[PASS]\nKeelBuilderInvariantTest invariants:\n[PASS] invariantBuilderAssetsStayWithRouterHelperOrTeam\n[PASS] invariantBuilderOperationsCannotConsumeBettorEth\n[PASS] invariantEverySuccessfulQueueConsumesExactlyOneScopedPermit\n KeelBuilderInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| BuilderHandler | advanceBlock    | 786   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimFromHelper | 752   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | claimTokens     | 718   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | credit          | 712   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | donateETH       | 735   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | enableHelper    | 764   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | mintHeld        | 774   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | queueHeld       | 763   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | recoverETH      | 724   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | spinAndRefund   | 713   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| BuilderHandler | withdrawETH     | 751   | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 57366309911980636\n  Bound result 32719032206016321727\n  Bound result 71996739901653383\n  Bound result 9531915015125499503\n  Bound result 3279\n  Bound result 1500000\n  Bound result 9999999999000000\n  Bound result 13\n  Bound result 1\n  Bound result 2678400\n  Bound result 20000000000000000\n  Bound result 27925494101429287909\n  Bound result 8420157447478343072\n  Bound result 89999999998321601\n  Bound result 100000000000000000\n  Bound result 100000000000000000\n  Bound result 19295979712995\n  Bound result 79099938193396609\n  Bound result 2906\n  Bound result 47426032271538023218\n  Bound result 9999999999999998\n  Bound result 15107\n  Bound result 5282\n  Bound result 90000000000000000\n  Bound result 10000000000000000\n  Bound result 450\n  Bound result 24576\n  Bound result 456\n  Bound result 2068\n  Bound result 80235064477554320363\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.24s (4.24s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1625)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 17      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 448     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 406     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 350     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 404     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000005171\n  Bound result 8713686547752044\n  Bound result 299000000000002825\n  Bound result 120000000000012289\n  Bound result 4979\n  Bound result 299000000000000208\n  Bound result 299000000000005736\n  Bound result 120000000000008167\n  Bound result 22230\n  Bound result 120000000000001096\n  Bound result 5387\n  Bound result 82754044108979615\n  Bound result 120000000000002105\n  Bound result 6790\n  Bound result 120000000000007115\n  Bound result 11019\n  Bound result 108979339827269763\n  Bound result 15151\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.26s (16.25s CPU time)\n\nRan 2 tests for test/KeelLifecycleInvariant.t.sol:KeelLifecycleInvariantTest\n[PASS]\nKeelLifecycleInvariantTest invariants:\n[PASS] invariantImdOnlyPaysOwnRequests\n[PASS] invariantIndependentEthFlowLedger\n[PASS] invariantLiabilitiesSharesAndTerminalStates\n KeelLifecycleInvariantTest invariants (runs: 128, calls: 12288, reverts: 0)\n\n╭------------------+-----------------+-------+---------+----------╮\n| Contract         | Selector        | Calls | Reverts | Discards |\n+=================================================================+\n| LifecycleHandler | badResult       | 794   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | cashOut         | 780   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | changeMarket    | 785   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | claimFees       | 773   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | decide          | 728   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | deposit         | 786   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | donate          | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | elapse          | 769   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | externalResolve | 772   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fulfill         | 748   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | fundAndSpin     | 1501  | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | request         | 750   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | result          | 801   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | sell            | 776   | 0       | 0        |\n|------------------+-----------------+-------+---------+----------|\n| LifecycleHandler | setPolicy       | 775   | 0       | 0        |\n╰------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 20000\n  Bound result 57680\n  Bound result 37666202615568730\n  Bound result 3878\n  Bound result 1\n  Bound result 14944806920267234\n  Bound result 299\n  Bound result 279999999999999994\n  Bound result 797\n  Bound result 16078718432980334\n  Bound result 15\n  Bound result 21411\n  Bound result 270000000000009168\n  Bound result 9\n  Bound result 25316124635545941\n  Bound result 7933\n  Bound result 20000\n  Bound result 49115\n  Bound result 903\n  Bound result 13\n  Bound result 3669829400\n  Bound result 117\n  Bound result 9001\n  Bound result 270000001080644453\n  Bound result 14362\n  Bound result 242\n  Bound result 7387\n  Bound result 17869\n  Bound result 32\n  Bound result 32423\n  Bound result 2941\n  Bound result 11\n  Bound result 270000000000005564\n  Bound result 3600\n  Bound result 1024\n  Bound result 3454\n  Bound result 242\n  Bound result 570000000000058845\n  Bound result 270000000000021169\n  Bound result 4045048728929996\n  Bound result 13\n  Bound result 6392\n  Bound result 58089233741591132\n  Bound result 100000000000000000\n  Bound result 44945212059388834\n  Bound result 7004\n  Bound result 257\n  Bound result 270000000000003934\n  Bound result 1800\n  Bound result 27000000000000000\n  Bound result 500\n  Bound result 242\n  Bound result 3432033553368324272\n  Bound result 15330\n  Bound result 0\n  Bound result 270000000000000014\n  Bound result 15963\n  Bound result 2999999999999778\n  Bound result 7233\n  Bound result 270000000000000009\n  Bound result 3705\n  Bound result 1815085818\n  Bound result 5599\n  Bound result 3600\n\n[PASS] testLifecycleHandlerExercisesOracleRefundFallbackAndExternalResolution() (gas: 12596440)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 86400\n  Bound result 123\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 25.93s (25.94s CPU time)\n\nRan 12 test suites in 25.93s (65.26s CPU time): 133 tests passed, 0 failed, 0 skipped (133 total tests)\n","passed":true},{"durationMs":58,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":890,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelAccountingProperties.t.sol\":201,\"test/KeelBase.t.sol\":121,\"test/KeelBuilderInvariant.t.sol\":302,\"test/KeelFailurePaths.t.sol\":369,\"test/KeelInvariant.t.sol\":168,\"test/KeelLifecycleInvariant.t.sol\":494,\"test/KeelOracle.t.sol\":414,\"test/KeelRevisionChecks.t.sol\":189,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":308,\"test/OracleConformance.t.sol\":95,\"test/TEST_COVERAGE.md\":35,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"bbef090e124f989527190fb3b8095d1f266e3e4037e20813364600a69da5365a","verifiedTreeHash":"acaf30328b98a3ff153dbf03d8fd439ce80bfc08","verifierVersion":"0.1.0+745c1db4"},{"checks":[{"durationMs":37718,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 37.63s\nCompiler run successful!\n","passed":true},{"durationMs":17118,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] testCallbackSelectorIsCanonical() (gas: 3601)\n[PASS] testDigestMatchesProtocolVector() (gas: 13369)\n[PASS] testOriginalProtocolSignatureVerifies() (gas: 70232)\n[PASS] testUint256AnswerFromVectorKeyDeliveredUnder200kGas() (gas: 2638298)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 11.86ms (7.11ms CPU time)\n\nRan 8 tests for test/KeelRouter.t.sol:KeelRouterTest\n[PASS] testBuilderETHWithdrawCannotTakePurchaseRefund() (gas: 885614)\n[PASS] testBuilderRevenueToTeamViaNextBlockHelper() (gas: 418545)\n[PASS] testEmergencyBuilderRecoveryUsesOnlyBuilderRevenue() (gas: 466031)\n[PASS] testHelperFailureRollsBackRewardClaim() (gas: 291225)\n[PASS] testHelperMustBeConfiguredBeforeClaim() (gas: 39754)\n[PASS] testLockedTokenCannotUseDirectTransfer() (gas: 422257)\n[PASS] testRewardDeadlineMinimumAndPermission() (gas: 215396)\n[PASS] testScopeRejectsSignaturesOutsideActiveOwnerTransfer() (gas: 25937)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 12.59ms (6.75ms CPU time)\n\nRan 11 tests for test/KeelSafety.t.sol:KeelSafetyTest\n[PASS] testApplicationRuntimeSizeAndForbiddenOpcodes() (gas: 17876771)\n[PASS] testBetCannotBlockInitialRouterWiring() (gas: 330181)\n[PASS] testOwnerHasNoBettorEthWithdrawalPower() (gas: 325807)\n[PASS] testPaidUUIDCannotBeReusedForAnotherOwnRequest() (gas: 4573630)\n[PASS] testParameterBounds() (gas: 193705)\n[PASS] testPrivilegedSettersRejectNonOwner() (gas: 320350)\n[PASS] testReentrantClaimCannotDrainOtherBettors() (gas: 2147722)\n[PASS] testReentrantWithdrawalCannotDrainOtherBettors() (gas: 801678)\n[PASS] testRejectingBettorCanClaimToAnotherAddress() (gas: 1877231)\n[PASS] testRejectingKeeperRollsBackSpinAndCanUseAnotherKeeper() (gas: 1286335)\n[PASS] testStaticConstructorsWorkWithNoDependencyCodeAndExplicitOwner() (gas: 367165)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 142.86ms (152.39ms CPU time)\n\nRan 22 tests for test/KeelSettlement.t.sol:KeelSettlementTest\n[PASS] testDepositorReclaimBackingDeliversWithoutReceiverHook() (gas: 1615787)\n[PASS] testDepositorResolvesDuringOracleRequest() (gas: 2833364)\n[PASS] testDepositorResolvesETHBeforeSyncAllocation() (gas: 1564821)\n[PASS] testFeeAppliesToBidNotUnsolicitedEth() (gas: 1733473)\n[PASS] testFeeCannotBeChangedForExistingBettors() (gas: 1690109)\n[PASS] testFeeCapAndFeeExcludesBuffer() (gas: 3113805)\n[PASS] testFinalizerResolvesFirst() (gas: 1597660)\n[PASS] testForwardNFTRejectsEthSettledRound() (gas: 1633186)\n[PASS] testRecoverProtocolStuckNFTAndForward() (gas: 1881048)\n[PASS] testRecoverStuckNFTRejectsEthSettledRound() (gas: 1677581)\n[PASS] testSaleFeeAndRoundingDustAllStayAccounted() (gas: 1966758)\n[PASS] testSaleProceedsProRataOnceIncludingBuffer() (gas: 2020322)\n[PASS] testSaleRequiresSuccessfulNFTForward() (gas: 1832033)\n[PASS] testSettlementFailureKeepsDecisionRetryable() (gas: 3191381)\n[PASS] testStrandedKeptNFTReleasesCashAfterGraceAndStaysForwardable() (gas: 2511278)\n[PASS] testStuckKeptNFTRemainsRecoverableAfterStrandedSettlement() (gas: 2271140)\n[PASS] testTeamCanBeUpdatedToRecoverForwarding() (gas: 1918876)\n[PASS] testTeamRejectingNFTDoesNotUndoKeepAndCanRetry() (gas: 2083751)\n[PASS] testUnauthorizedAndInvalidSaleProceedsRejected() (gas: 1674653)\n[PASS] testUndeliverableKeepCanEventuallyUseEthFallback() (gas: 3266672)\n[PASS] testUnrelatedNFTCannotEnterSafeReceiver() (gas: 152639)\n[PASS] testUnsolicitedEthCannotBeSweptAndBenefitsResolvingRound() (gas: 1660106)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 1.99s (64.51ms CPU time)\n\nRan 35 tests for test/KeelOracle.t.sol:KeelOracleTest\n[PASS] testAnswerAtMissingValueCutoffCannotRevokeFallback() (gas: 2860345)\n[PASS] testAttestationCannotCrossConsumerDomain() (gas: 2555121)\n[PASS] testBadSignatureTamperingAndReplayRejected() (gas: 2843717)\n[PASS] testBodyUsesAllocatedNFTAndExactDefinitions() (gas: 2790085)\n[PASS] testColdOracleCallbackFits200kGas() (gas: 2644719)\n[PASS] testDecisionReadsDiscountLive() (gas: 2987494)\n[PASS] testDefinitionsEscapedAndBounded() (gas: 1575265)\n[PASS] testExpiredStoredAnswerNeedsFallback() (gas: 3121436)\n[PASS] testFallbackAtLeadClearsPendingAndRejectsLateAnswer() (gas: 2926376)\n[PASS] testFallbackFreshFloorUsesMarginAndStrictComparison() (gas: 2849340)\n[PASS] testFallbackFreshHighFloorKeeps() (gas: 2847602)\n[PASS] testFallbackNoFloorUsesOwnerSetDefault() (gas: 1577844)\n[PASS] testFloorFailureIsUnavailable() (gas: 2535646)\n[PASS] testFreshFloorBoundaryAndFutureTimestamp() (gas: 2596593)\n[PASS] testIntakePriceChangeDoesNotOverpay() (gas: 1393514)\n[PASS] testInvalidPanelChainTypeExpiryAndBoundsRejected() (gas: 3094852)\n[PASS] testLastMomentPaidAskStillAccepted() (gas: 2748155)\n[PASS] testLiveOneHourWindowLeavesRoomForPaidOracleDecision() (gas: 3094249)\n[PASS] testLowImdDoesNotSpendBettorEthAndCanBeRetriedAfterTopUp() (gas: 2886802)\n[PASS] testNoPaidAskWhenAnswerCouldNotLandBeforeFallback() (gas: 1690033)\n[PASS] testNonIntakeAndUnknownCallbacksRejected() (gas: 2573301)\n[PASS] testObviousKeep30EthFloorVersus1EthBacking() (gas: 1625635)\n[PASS] testOracleAboveMarginKeepsAndCallbackOnlyStores() (gas: 2985752)\n[PASS] testOracleAtMarginTakesEth() (gas: 2985958)\n[PASS] testOracleBelowMarginAndZeroAreValidNoAnswers() (gas: 2907596)\n[PASS] testOracleCannotRotateDuringPendingRound() (gas: 2543968)\n[PASS] testOracleFailureFallbackImmediatelyAndNoSecondPaidRequest() (gas: 2960680)\n[PASS] testOwnerCannotFlipAllocatedRoundThroughFloorOrPolicy() (gas: 2081092)\n[PASS] testResolvedRoundReentryRetriesForwardInsteadOfReverting() (gas: 2385169)\n[PASS] testShortLiveWindowSaturatesAndDoesNotPayOracle() (gas: 1587071)\n[PASS] testStaleFloorSkipsPrecheckAndPaysExactIMD() (gas: 2571808)\n[PASS] testTimingParametersMustFitLiveWindow() (gas: 140198)\n[PASS] testValidAnswerPreventsEarlyFallbackButDeadlineDoesNotLock() (gas: 3146014)\n[PASS] testZeroLeadNeverPaysForAnUnanswerableQuestion() (gas: 1668710)\n[PASS] testZeroLeadStillAsksOneSecondBeforeFallback() (gas: 2768117)\nSuite result: ok. 35 passed; 0 failed; 0 skipped; finished in 2.15s (139.80ms CPU time)\n\nRan 25 tests for test/KeelRounds.t.sol:KeelRoundsTest\n[PASS] test501stBettorCarriedToNextRound() (gas: 57649390)\n[PASS] testAcquireFailureRollsBackAllAccountingAndReward() (gas: 563819)\n[PASS] testAwaitingSaleStakeDoesNotBlockMigrationButOpenStakeDoes() (gas: 3491742)\n[PASS] testBlackoutBothDailyWindowsAndBoundaries() (gas: 805940)\n[PASS] testBufferAndImmediateRefundBelongToBettors() (gas: 1801239)\n[PASS] testCarryOverAndWithdraw() (gas: 559853)\n[PASS] testDependenciesCannotBeReplacedUnderExistingBets() (gas: 348107)\n[PASS] testExpiredRequestRefundAndRetry() (gas: 2380039)\n[PASS] testFuzzUpTo500BettorsAndProRataConservation(uint16,uint64) (runs: 128, μ: 29610324, ~: 18395242)\nLogs:\n  Bound result 1\n  Bound result 5744073709549563\n\n[PASS] testFuzzUpTo500SettledBettors(uint16,uint32) (runs: 128, μ: 30348085, ~: 19436844)\nLogs:\n  Bound result 1\n  Bound result 4294967292\n\n[PASS] testGasCap() (gas: 335438)\n[PASS] testHugeBetAtomicBound() (gas: 3276336)\n[PASS] testMinimumAppliesToTransactionNotCarryFragment() (gas: 420446)\n[PASS] testMultipleRoundCarryOver() (gas: 711775)\n[PASS] testNoonBoundaryAllowsSpin() (gas: 659654)\n[PASS] testOnlyOneUnresolvedRequestButNextRoundCanCollect() (gas: 2055336)\n[PASS] testPauseOnlyStopsBets() (gas: 1085877)\n[PASS] testPendingRequestCannotWithdrawClaimOrDecide() (gas: 974899)\n[PASS] testPriceRiseInVrfFeeUsesTransactionGasPrice() (gas: 390951)\n[PASS] testPriceRiseKeepsRoundOpenAndTopUpUsesLivePrice() (gas: 1029193)\n[PASS] testProtocolBlackoutAlsoChecked() (gas: 337920)\n[PASS] testRefundReopensRoundAndSharesVrfAndRewardLoss() (gas: 1225086)\n[PASS] testRewardClampsToTwoPercent() (gas: 695940)\n[PASS] testSpinRewardAndRouterGuards() (gas: 875063)\n[PASS] testUnclaimedSettledStakeDoesNotBlockMigration() (gas: 3604939)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 2.15s (4.29s CPU time)\n\nRan 1 test for test/KeelInvariant.t.sol:KeelInvariantTest\n[PASS]\nKeelInvariantTest invariants:\n[PASS] invariantAtMostOneUnresolvedSpin\n[PASS] invariantClaimablePlusOpenBalancesNeverExceedEth\n KeelInvariantTest invariants (runs: 64, calls: 4096, reverts: 1621)\n\n╭--------------+-----------+-------+---------+----------╮\n| Contract     | Selector  | Calls | Reverts | Discards |\n+=======================================================+\n| RoundHandler | bet       | 441   | 14      | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claim     | 467   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | claimFee  | 467   | 454     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | decide    | 482   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | fulfill   | 451   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | movePrice | 458   | 0       | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | sell      | 410   | 407     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | spin      | 476   | 349     | 0        |\n|--------------+-----------+-------+---------+----------|\n| RoundHandler | withdraw  | 444   | 397     | 0        |\n╰--------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 120000000000002687\n  Bound result 3669\n  Bound result 299000000000002869\n  Bound result 120000000000012170\n  Bound result 6116\n  Bound result 299000000000000319\n  Bound result 299000000000005841\n  Bound result 120000000000008201\n  Bound result 4172\n  Bound result 120000000000000730\n  Bound result 5624\n  Bound result 82754044108979615\n  Bound result 120000000000001150\n  Bound result 6948\n  Bound result 120000000000007161\n  Bound result 1049126309\n  Bound result 66669652837796515\n  Bound result 15217\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 17.03s (17.03s CPU time)\n\nRan 7 test suites in 17.03s (23.49s CPU time): 106 tests passed, 0 failed, 0 skipped (106 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeelFwaRouter.claimBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.purchase(uint256,uint256,uint256)\",\"KeelFwaRouter.queueHeldBuilderRewards(uint256,uint256)\",\"KeelFwaRouter.receive()\",\"KeelFwaRouter.recoverBuilderAllowanceAsETH()\",\"KeelFwaRouter.renounceOwnership()\",\"KeelFwaRouter.setPool(address)\",\"KeelFwaRouter.setTransferHelper(address)\",\"KeelFwaRouter.transferOwnership(address)\",\"KeelFwaRouter.withdrawBuilderETH()\",\"KeelSpinVault.bet()\",\"KeelSpinVault.betInto(uint256)\",\"KeelSpinVault.claim(uint256)\",\"KeelSpinVault.claimFees(address)\",\"KeelSpinVault.claimTo(uint256,address)\",\"KeelSpinVault.depositSaleProceeds(uint256,bytes32)\",\"KeelSpinVault.executeDecision(uint256)\",\"KeelSpinVault.forwardNFT(uint256)\",\"KeelSpinVault.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"KeelSpinVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"KeelSpinVault.receive()\",\"KeelSpinVault.recoverStuckNFT(uint256)\",\"KeelSpinVault.renounceOwnership()\",\"KeelSpinVault.requestDecision(uint256)\",\"KeelSpinVault.setBetParameters(uint256,uint16,uint16)\",\"KeelSpinVault.setBetsPaused(bool)\",\"KeelSpinVault.setDecisionParameters(uint16,uint16,uint256,uint256,uint256,uint256,bool)\",\"KeelSpinVault.setDefinition(uint256,string)\",\"KeelSpinVault.setFeeBps(uint16)\",\"KeelSpinVault.setFloorOracle(address)\",\"KeelSpinVault.setFwa(address)\",\"KeelSpinVault.setIMD(address)\",\"KeelSpinVault.setIntake(address)\",\"KeelSpinVault.setOraclePayment(bytes32,uint256,uint256)\",\"KeelSpinVault.setOracleSigner(address)\",\"KeelSpinVault.setRouter(address)\",\"KeelSpinVault.setSpinParameters(uint256,uint256,uint256,uint16)\",\"KeelSpinVault.setTeamWallet(address)\",\"KeelSpinVault.settleFallback(uint256)\",\"KeelSpinVault.spin(uint256)\",\"KeelSpinVault.syncRound(uint256)\",\"KeelSpinVault.transferOwnership(address)\",\"KeelSpinVault.withdraw(uint256)\",\"KeelSpinVault.withdrawIMD(address,uint256)\",\"KeelSpinVault.withdrawTo(uint256,address)\"],\"files\":{\".gitignore\":4,\"README.md\":133,\"docs/SECURITY.md\":41,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":21,\"docs/oracle-preflight.json\":31,\"docs/verified-sources.json\":46,\"foundry.toml\":21,\"launch.json\":26,\"src/KeelFwaRouter.sol\":182,\"src/KeelSpinVault.sol\":893,\"src/OracleAttestation.sol\":221,\"src/OracleBody.sol\":49,\"src/interfaces/IKeelProtocols.sol\":122,\"test/KeelBase.t.sol\":121,\"test/KeelInvariant.t.sol\":168,\"test/KeelOracle.t.sol\":445,\"test/KeelRounds.t.sol\":392,\"test/KeelRouter.t.sol\":111,\"test/KeelSafety.t.sol\":242,\"test/KeelSettlement.t.sol\":357,\"test/OracleConformance.t.sol\":95,\"test/mocks/BuilderRewards.sol\":135,\"test/mocks/Protocols.sol\":327},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f37e44cdcb6e67bb7e90c88b83d1d8700d1575dd996724d63c0e14a67f8d7cba","verifiedTreeHash":"bbd1286252f3858c9d9c8bff7665e3fe9658f180","verifierVersion":"0.1.0+745c1db4"}]}