{"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":"b951fadc-6bb4-4a14-862b-f3a254064824","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"b176c03d12be7cf1e495390b82acabe6f75d205da74175613b3679229ceed241","dependsOn":["build_contract_project","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"c26edc76dc7a76e09a42c3634054429c1679c6e247747d647a03f77e5b332d7e","skillId":"adversarial-review","tools":[]},"key":"adversarial_review","kind":"code","role":"review","skillHash":"c26edc76dc7a76e09a42c3634054429c1679c6e247747d647a03f77e5b332d7e","skillId":"adversarial-review","state":"accepted"},{"acceptedSubmissionHash":"be386e9a16381c7726fe75e472e75c93b7e4cf1c4f52cfdc191f179ff0df5e46","dependsOn":[],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"47381b166d1f406a34f0ebdc740ee21bc0383e56e7ad9084b26abf1bf7570904","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"47381b166d1f406a34f0ebdc740ee21bc0383e56e7ad9084b26abf1bf7570904","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"404dd0b5327d38d7a81e054f0b0acca288752b3992da790f45c8397aeb216246","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"}],"objective":"CallBook: an on-chain, tamper-proof track record for trading calls. Traders publish price calls without revealing them early, and the contract settles each call against Chainlink prices, so anyone can check a caller's real hit rate instead of trusting screenshots.\n\nContract CallBook (Solidity, Foundry, deployed on Sepolia):\n- Markets: ETH/USD (Chainlink 0x694AA1769357215DE4FAC081bf1f309aDC325306) and BTC/USD (0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43) on Sepolia; the owner can add or disable a feed, nothing else.\n- commit(bytes32 hash, uint64 expiry): a caller commits keccak256(abi.encode(caller, feed, direction, targetPrice, expiry, salt)); records the Chainlink price at commit time. Expiry between 1 hour and 30 days from now.\n- reveal(id, feed, direction, targetPrice, salt): only the caller, only after expiry and within 24 hours after it; checks the hash. direction is UP or DOWN; targetPrice uses the feed's decimals and must be on the correct side of the price at commit.\n- settle(id, roundId): anyone can settle a revealed call. The call is a HIT if any Chainlink round with updatedAt between commit time and expiry reached the target (price >= target for UP, <= target for DOWN); the settler passes the roundId that proves it and the contract checks that round's timestamp and answer. With no proof after reveal it settles as a MISS. Reject stale or incomplete rounds.\n- A call that is not revealed within 24 hours after expiry counts as a MISS for its caller (anyone can mark it), so callers cannot hide bad calls.\n- Per caller: calls, hits, misses, current streak and best streak, readable in one view call; events for every commit, reveal and settlement.\n- No tokens, no fees, no ETH held; no admin power over results.\n\nTests: commit-reveal hash checks, wrong caller, late reveal, a hit proven by an intermediate round, a miss, forced miss for unrevealed calls, stale rounds, feed decimals, fuzz tests on prices and expiries, using a mock aggregator. README with the rules and a worked example.","parentJobId":null,"planHash":"4e1629e1107e7dce0cceaf79b5dd80cd5177dba52e4fd2de6c43b07202598244","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"b951fadc-6bb4-4a14-862b-f3a254064824","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-376-callbook-on-chain-tamper-proof-track"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50955","feedbackHash":"5e9b227be3156dd9c2312631d67b42107d9742ce13d7f8d8b496c210717af35b","nodeKey":"adversarial_review","submissionHash":"b176c03d12be7cf1e495390b82acabe6f75d205da74175613b3679229ceed241","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50959","feedbackHash":"19e644566688b0302e25d65e58025dbbf1fd8f421565ad9f29417f1e884a4395","nodeKey":"build_contract_project","submissionHash":"be386e9a16381c7726fe75e472e75c93b7e4cf1c4f52cfdc191f179ff0df5e46","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50971","feedbackHash":"1455e658e418083b9d89381fe0e1979e35dcad2d650cd8dc430b68fa13a23012","nodeKey":"manifest","submissionHash":"404dd0b5327d38d7a81e054f0b0acca288752b3992da790f45c8397aeb216246","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9aac0999fea5a3b298ffd60fec27dfb4b75d94403339a01538fa2b9c90807d93","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"e1d019bfcbe5a11cd206f9047e3f91ea080db9f2a93ea61d87b401fa7dd670f6","device":"35c52a5b502e847c","findings":[],"hash":"404dd0b5327d38d7a81e054f0b0acca288752b3992da790f45c8397aeb216246","nodeId":"ad8650ae-c2ff-4f39-8b9b-27d61da50ae6","outcome":"completed","summary":"Created [launch.json](/home/identitymd5/.identitymd/work/b951fadc-6bb4-4a14-862b-f3a254064824/ad8650ae-c2ff-4f39-8b9b-27d61da50ae6/launch.json) with matching token metadata, `$owner`, Sepolia feeds, and native-ETH pool settings.\n\nValidation passed:\n- Schema and constructor ABI checks.\n- `forge build --offline` (existing lint warnings).\n- `forge test --offline`: 57 passed, including pinned checks.\n\nOnly `launch.json` is changed for submission.","treeHash":"50b233360d81be8f5fd940eeaa2ad722e414d524","usage":{"cachedInputTokens":207872,"inputTokens":29207,"model":"gpt-6-astra","outputTokens":3606,"runtime":"codex","turns":4,"wallClockMs":128528}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3c7630b22a73c1fb","findings":[{"description":"Streaks are updated in _finish in settlement order. A revealed HIT has no settlement deadline (settle with a proof round is accepted at any time while Revealed), and the caller can settle their own MISS with roundId 0 right after revealing. So the caller can record every miss as it happens, keep hits sitting in Revealed, and settle all the hits in one batch at the end. bestStreak then equals the number of hits, whatever order the calls actually expired in. That defeats the per-caller 'current streak and best streak' requirement of a tamper-proof record. README/REVIEW #16 say streaks follow settlement order, but neither mentions that the caller chooses that order. Existing test test_stats_streaksFollowSettlementOrder enshrines the behaviour and never checks that it can be manipulated. Fix direction (needs a scope decision): derive streaks from expiry order, or require hit proofs within the reveal window so that settlement order is bounded.","line":336,"path":"src/CallBook.sol","reproduction":"Mock ETH feed at 2000e8. Alice commits 5 UP calls with expiries T0+1d..T0+5d, targets [2100e8, 3000e8, 2100e8, 3000e8, 2100e8] (expiry order H,M,H,M,H; honest best streak = 1). Push round r = 2200e8 at T0+1h. At each expiry Alice calls reveal(...). For the two 3000e8 calls she immediately calls settle(id, 0). She leaves the three hits Revealed and after day 5 calls settle(id, r) on each. Expected: bestStreak 1, currentStreak 1. Actual: getStats(alice) = {hits 3, misses 2, bestStreak 3, currentStreak 3}. Verified with a scratch forge test (test_streakReorder) that passed.","severity":"medium","title":"Caller controls settlement order, so currentStreak/bestStreak can be inflated to the total number of hits"},{"description":"commit() skips disabled feeds and still succeeds. If the call's hidden feed was disabled in the same block (or any block before the commit is mined), commitPriceOf[id][feed] stays 0 and reveal always reverts with FeedNotRecorded. The call cannot be cancelled, so it can only end as a markUnrevealed forced MISS. The owner can therefore push misses onto a caller's record by front-running their commit with disableFeed (with 2 feeds, a 50% blind guess, or certainty against a caller known to trade one market). An honest disable also silently dooms every commit still in the mempool. This conflicts with 'no admin power over results' and with the README claim that disabling a feed does not affect calls. The only test, test_reveal_feedNotRecordedAtCommit, covers a feed added after commit. It does not cover a disable racing a commit. Fix direction that keeps the design: let the caller name a feed set or expected feed count in commit and revert if a named feed is not enabled, or let a call whose committed feed was never recorded be voided instead of forced to MISS.","line":216,"path":"src/CallBook.sol","reproduction":"Setup: feeds ETH and BTC enabled. In one block: owner calls disableFeed(btc), then alice calls commit(keccak256(abi.encode(alice, btc, UP, 61000e8, e, salt)), e) with e = T0+1d. The commit succeeds. BTC then trades at 65000e8 (round inside the window, so the call really hit). At expiry, reveal(id, btc, UP, 61000e8, salt) reverts FeedNotRecorded. After e+24h+1, markUnrevealed(id) gives getStats(alice).misses == 1. Expected: the call is revealable or voided, and the owner cannot affect the result. Verified with scratch test test_ownerDisableForcesMiss.","severity":"medium","title":"Owner disableFeed landing before a pending commit turns that call into a guaranteed forced MISS (admin power over results)"},{"description":"reveal only requires the target to be strictly beyond the commit price, so a target 1 unit (1e-8 USD) away is valid. Sepolia ETH/USD and BTC/USD post a round about every 3,600 s, and each new answer differs from the last. A 24h call therefore spans ~24 rounds, which almost always include one above and one below the commit price. A caller can hedge with UP+1 and DOWN-1 in the same block and get two hits with zero skill. A single UP+1 call is also close to a sure hit whenever the off-chain market already sits above the lagging on-chain answer, or when a pending oracle transmit is visible. The hit rate the product exists to publish can be farmed, although Revealed events do expose commitPrice and target, so careful readers can discount these calls. This matches the literal 'correct side' rule in the spec, so the fix is a scope decision, e.g. a minimum relative distance or a minimum number of rounds, and not a code bug.","line":274,"path":"src/CallBook.sol","reproduction":"Real Sepolia ETH/USD rounds 18446744073709587924..926: 268592767666 then 268889842588 (+3636 s) then 268013420000 (+7272 s). Mock the latest at 268592767666. Alice commits UP target 268592767667 and DOWN target 268592767665, both expiring T0+1d. After expiry: revealAndSettle(up,...,r1=268889842588) and revealAndSettle(dn,...,r2=268013420000) both succeed. getStats(alice) = {hits 2, misses 0, bestStreak 2} from a position with no view on direction. Verified with scratch test test_bothSidesHit.","severity":"medium","title":"No minimum target distance: committing UP commitPrice+1 and DOWN commitPrice-1 together yields two HITs on normal hourly price noise"},{"description":"Once block.timestamp > expiry + 24h, settle(id, 0) from anyone records a MISS without checking whether a HIT round exists. If the caller revealed with reveal() (not revealAndSettle) near the end of the window, or their settle(id, proof) tx lands after the window, a griefer can front-run it and make the MISS permanent. The call then reads as a MISS even though Chainlink data proves a HIT, against the spec rule that a call is a HIT if any in-window round reached the target. README tells callers to use revealAndSettle, which mitigates this. The contract does not enforce it. This only affects callers who did not settle in the same transaction, hence low.","line":303,"path":"src/CallBook.sol","reproduction":"Alice commits UP 2100e8 (ETH at 2000e8), expiry e. Round r = 2200e8 at T0+1h. At e+24h alice calls reveal(id, eth, UP, 2100e8, salt). At e+24h+1 griefer calls settle(id, 0), which succeeds. Alice's settle(id, r) then reverts NotRevealed, and getStats(alice) = {hits 0, misses 1}. Expected: a provable HIT cannot be overwritten as a MISS. Verified with scratch test test_postWindowMissOverProvableHit.","severity":"low","title":"After the reveal window any third party can record a MISS on a revealed call that has a valid proving round"}],"hash":"b176c03d12be7cf1e495390b82acabe6f75d205da74175613b3679229ceed241","nodeId":"aafffef4-cd49-4f75-a394-bb56808181b7","outcome":"completed","summary":"I found four problems in CallBook, none high: three medium and one low. I reproduced each with a scratch Foundry test in a copy at `/tmp/cbpoc`, and all four passed. I didn't change any repository file; the findings are in `.imd-findings.json`, which is valid JSON and doesn't show up in `git status`.\n\n**Medium: the caller controls their own streak** (`src/CallBook.sol:336`). Streaks count in the order calls are settled, and the caller can choose that order: a revealed HIT has no deadline for settling, and the caller can settle their own MISS straight away. Five calls that expire in the order hit, miss, hit, miss, hit should give a best streak of 1. By settling the misses as they happen and settling the three hits together at the end, the caller gets `bestStreak = 3` and `currentStreak = 3`.\n\n**Medium: the owner can turn a call into a MISS** (`src/CallBook.sol:216`). If `disableFeed(btc)` lands before a pending `commit` for a BTC call, the commit still succeeds but records no BTC price. Reveal then always fails with `FeedNotRecorded`, and the call can only end as a forced MISS, even when BTC reached the target. That breaks \"no admin power over results\", and it also happens when the owner disables a feed honestly while commits are waiting to be mined.\n\n**Medium: hits can be farmed with tiny targets** (`src/CallBook.sol:274`). A target only has to be one unit (1e-8 USD) past the commit price. I used real Sepolia ETH/USD rounds, which post roughly once an hour. An UP call at commit price + 1 and a DOWN call at commit price − 1, made together, both settled as hits within two rounds. This follows the spec's literal \"correct side\" rule, so fixing it (for example with a minimum distance) is a scope decision, not a code bug.\n\n**Low: a proven hit can be recorded as a MISS** (`src/CallBook.sol:303`). Once the 24-hour reveal window closes, anyone can call `settle(id, 0)` and record a MISS without any check for a round that proves a hit. If the caller revealed late or settles after the window, a third party can get in first and the MISS is permanent. Using `revealAndSettle` avoids it, and the README recommends that, but the contract doesn't require it.\n\nI also checked these and found them sound:\n- **Hash:** it binds every field, and expiry is read from storage.\n- **Time bounds:** the expiry, reveal-window and proof-round boundaries hold at their exact edges.\n- **Round checks:** stale, incomplete and non-positive rounds are rejected.\n- **Owner:** it is immutable and set from `$owner` in `launch.json`.\n- **Funds:** the contract holds no ETH or tokens and has no reentrancy surface.\n- **LaunchToken:** fixed supply, with no owner or mint function.\n- **Staleness limit:** both live Sepolia feeds update every ~3,600 s, so the 3-hour limit won't block commits in normal operation.","treeHash":null,"usage":{"cachedInputTokens":649269,"inputTokens":24,"model":"claude-fable-5-1","outputTokens":17617,"runtime":"claude","turns":13,"wallClockMs":357418}},{"artifacts":[],"attempt":1,"bundleHash":"36ed78f2e9c81acdec139963dc8b6718c162c793ec04fef8321180a57b2240bc","device":"468e82a89b9bfe18","findings":[],"hash":"be386e9a16381c7726fe75e472e75c93b7e4cf1c4f52cfdc191f179ff0df5e46","nodeId":"0ed16d29-7cf5-411b-bf94-318df00810ff","outcome":"completed","summary":"Everything is in place and verified.\n\n**Delivered** a fresh Foundry project for CallBook, the on-chain commit-reveal track record settled against Chainlink rounds.\n\n- `src/CallBook.sol` implements commit (snapshots every enabled feed's fresh price, since the hash hides the market), reveal (caller only, from expiry to expiry plus 24 hours, hash and target-side checks), settle (HIT proven by any in-window round, stale and incomplete rounds rejected, MISS without proof), and forced MISS for unrevealed calls. Per-caller stats with current and best streak come from one view call, and every step emits an event.\n- **Griefing guard** worth noting: a third party can only settle a no-proof MISS after the reveal window closes, so nobody can front-run a caller's proof. The caller can settle a MISS at any time or use `revealAndSettle` to prove atomically.\n- `src/LaunchToken.sol` is the required fixed-supply ERC-20 with no admin paths.\n- `script/Deploy.s.sol` exposes a single `deploy(Config)` function that a test calls directly, and its dry run passes with an empty environment.\n- `README.md` covers rules, a worked example, assumptions, factory constructor arguments, and owner, caller, and keeper responsibilities. `REVIEW.md` records the self-review and untested edges. ABIs are in `docs/abi/`.\n\n**Verification results**\n\n| Check | Result |\n| --- | --- |\n| `forge build --offline` | pass, solc 0.8.26 pinned |\n| `forge test --offline` | 49 tests pass, 6 fuzz |\n| `forge fmt --check` | clean |\n| Deploy script dry run | pass |\n| Scratch floor harness | pass, CREATE2 via factory, no forbidden opcodes |\n\nThe scratch harness in `test/scratch/` mirrors the protected deployment floor and is discarded before submission. The constructor makes no external calls, so it deploys even where the feed addresses hold no code.\n\n**Not done:** I did not commit. Git in this sandbox cannot read the global config, and the network's own process handles the commit. `lib/forge-std` is vendored as plain files with no git metadata.","treeHash":"768f298706ba23df1adc1ab3728ffa84dbdc36b7","usage":{"cachedInputTokens":1552022,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":51857,"runtime":"claude","turns":23,"wallClockMs":580693}}],"verification":[{"checks":[{"durationMs":1505,"exitCode":0,"name":"build","output":"Compiling 28 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.44s\nCompiler run successful!\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:37:9\n   │\n37 │         token.transfer(deployer, 1);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:202:13\n    │\n202 │         if (expiry < block.timestamp + MIN_DURATION || expiry > block.timestamp + MAX_DURATION) revert BadExpiry();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:202:56\n    │\n202 │         if (expiry < block.timestamp + MIN_DURATION || expiry > block.timestamp + MAX_DURATION) revert BadExpiry();\n    │                                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:44:9\n   │\n44 │         token.transferFrom(deployer, bob, 3e18);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:232:60\n    │\n232 │         if (updatedAt == 0 || answeredInRound < roundId || updatedAt > block.timestamp) revert StalePrice(feed);\n    │                                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:233:13\n    │\n233 │         if (block.timestamp - updatedAt > MAX_PRICE_AGE) revert StalePrice(feed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:48:9\n   │\n48 │         token.transferFrom(deployer, bob, 3e18);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:61:9\n   │\n61 │         token.transfer(to, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:265:13\n    │\n265 │         if (block.timestamp < c.expiry) revert NotExpired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:266:13\n    │\n266 │         if (block.timestamp > uint256(c.expiry) + REVEAL_WINDOW) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ test/CallBook.t.sol:657:19\n    │\n657 │         bool ok = expiry >= block.timestamp + book.MIN_DURATION() && expiry <= block.timestamp + book.MAX_DURATION();\n    │                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ test/CallBook.t.sol:657:70\n    │\n657 │         bool ok = expiry >= block.timestamp + book.MIN_DURATION() && expiry <= block.timestamp + book.MAX_DURATION();\n    │                                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:304:43\n    │\n304 │             if (msg.sender != c.caller && block.timestamp <= uint256(c.expiry) + REVEAL_WINDOW) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:326:13\n    │\n326 │         if (block.timestamp <= uint256(c.expiry) + REVEAL_WINDOW) revert RevealWindowOpen();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n   ╭▸ test/CallBook.t.sol:64:16\n   │\n64 │         return uint64(block.timestamp + duration);\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/CallBook.t.sol:160:37\n    │\n160 │         emit Committed(1, alice, h, uint64(T0), expiry);\n    │                                     ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/CallBook.t.sol:171:33\n    │\n171 │         assertEq(c.committedAt, uint64(T0));\n    │                                 ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\n","passed":true},{"durationMs":105,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployWiresConstructorArguments() (gas: 3540125)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 674.76µs (311.36µs CPU time)\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(uint256,address) (runs: 256, μ: 44796, ~: 44807)\n[PASS] test_metadataAndSupply() (gas: 20733)\n[PASS] test_noMintPath() (gas: 8779)\n[PASS] test_transfer() (gas: 46211)\n[PASS] test_transferFromRespectsAllowance() (gas: 72705)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 15416)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.83ms (4.03ms CPU time)\n\nRan 42 tests for test/CallBook.t.sol:CallBookTest\n[PASS] testFuzz_commit_expiryWindow(uint64) (runs: 256, μ: 131248, ~: 217952)\n[PASS] testFuzz_reveal_targetSideCheck(int256,int256,bool) (runs: 256, μ: 333584, ~: 355968)\n[PASS] testFuzz_settle_downHitIffAnswerReachesTarget(int256,int256,int256) (runs: 256, μ: 469661, ~: 453688)\n[PASS] testFuzz_settle_roundTimestampWindow(uint256) (runs: 256, μ: 404161, ~: 416904)\n[PASS] testFuzz_settle_upHitIffAnswerReachesTarget(int256,int256,int256) (runs: 256, μ: 472651, ~: 454173)\n[PASS] test_addFeed_boundedByMaxFeeds() (gas: 6140272)\n[PASS] test_addFeed_rejectsNonAggregator() (gas: 15936)\n[PASS] test_commit_expiryBounds() (gas: 347558)\n[PASS] test_commit_recordsPricesStatsAndEvent() (gas: 241897)\n[PASS] test_commit_rejectsEmptyHash() (gas: 11253)\n[PASS] test_commit_rejectsIncompleteLatestRound() (gas: 469988)\n[PASS] test_commit_rejectsNonPositiveAnswer() (gas: 188888)\n[PASS] test_commit_rejectsStaleFeed() (gas: 210359)\n[PASS] test_commit_skipsDisabledFeedAndNeedsAtLeastOne() (gas: 357102)\n[PASS] test_constructor_listsAndEnablesFeeds() (gas: 31915)\n[PASS] test_constructor_rejectsZeroAddresses() (gas: 77905)\n[PASS] test_contract_holdsNoEth() (gas: 17698)\n[PASS] test_feedDecimals_targetsUseTheFeedScale() (gas: 1130743)\n[PASS] test_hashCall_matchesOffchainEncoding() (gas: 11706)\n[PASS] test_markUnrevealed_forcesMissAfterWindow() (gas: 271154)\n[PASS] test_markUnrevealed_rejectsRevealedOrUnknown() (gas: 274510)\n[PASS] test_nonOwner_cannotManageFeeds() (gas: 15272)\n[PASS] test_owner_canAddAndDisableFeeds() (gas: 448130)\n[PASS] test_owner_disableRevertsWhenNotEnabled() (gas: 16633)\n[PASS] test_reveal_andSettleStillWorkAfterFeedDisabled() (gas: 426202)\n[PASS] test_reveal_beforeExpiry() (gas: 220737)\n[PASS] test_reveal_feedNotRecordedAtCommit() (gas: 750526)\n[PASS] test_reveal_happyPath() (gas: 279628)\n[PASS] test_reveal_hashMismatch() (gas: 233661)\n[PASS] test_reveal_late() (gas: 274329)\n[PASS] test_reveal_targetMustBeOnCorrectSide() (gas: 484185)\n[PASS] test_reveal_unknownOrSettledCall() (gas: 276041)\n[PASS] test_reveal_wrongCaller() (gas: 222533)\n[PASS] test_settle_hitAtExactTargetAndBoundaries() (gas: 865342)\n[PASS] test_settle_hitProofAcceptedAfterRevealWindow() (gas: 423521)\n[PASS] test_settle_hitProvenByIntermediateRound() (gas: 614841)\n[PASS] test_settle_missWithoutProof_byCaller() (gas: 309392)\n[PASS] test_settle_rejectsRoundsOutsideWindow() (gas: 376656)\n[PASS] test_settle_rejectsStaleAndIncompleteRounds() (gas: 529736)\n[PASS] test_settle_requiresRevealedAndSettlesOnce() (gas: 422040)\n[PASS] test_settle_thirdPartyMissOnlyAfterRevealWindow() (gas: 308228)\n[PASS] test_stats_streaksFollowSettlementOrder() (gas: 1295395)\nSuite result: ok. 42 passed; 0 failed; 0 skipped; finished in 18.12ms (82.59ms CPU time)\n\nRan 3 test suites in 18.93ms (22.63ms CPU time): 49 tests passed, 0 failed, 0 skipped (49 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"404dd0b5327d38d7a81e054f0b0acca288752b3992da790f45c8397aeb216246","verifiedTreeHash":"50b233360d81be8f5fd940eeaa2ad722e414d524","verifierVersion":"0.1.0+ff982c0f"},{"checks":[{"durationMs":1446,"exitCode":0,"name":"build","output":"Compiling 28 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.38s\nCompiler run successful!\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:37:9\n   │\n37 │         token.transfer(deployer, 1);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:44:9\n   │\n44 │         token.transferFrom(deployer, bob, 3e18);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:202:13\n    │\n202 │         if (expiry < block.timestamp + MIN_DURATION || expiry > block.timestamp + MAX_DURATION) revert BadExpiry();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ test/CallBook.t.sol:657:19\n    │\n657 │         bool ok = expiry >= block.timestamp + book.MIN_DURATION() && expiry <= block.timestamp + book.MAX_DURATION();\n    │                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ test/CallBook.t.sol:657:70\n    │\n657 │         bool ok = expiry >= block.timestamp + book.MIN_DURATION() && expiry <= block.timestamp + book.MAX_DURATION();\n    │                                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:48:9\n   │\n48 │         token.transferFrom(deployer, bob, 3e18);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:202:56\n    │\n202 │         if (expiry < block.timestamp + MIN_DURATION || expiry > block.timestamp + MAX_DURATION) revert BadExpiry();\n    │                                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value\n   ╭▸ test/LaunchToken.t.sol:61:9\n   │\n61 │         token.transfer(to, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n   ╭▸ test/CallBook.t.sol:64:16\n   │\n64 │         return uint64(block.timestamp + duration);\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint64' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:232:60\n    │\n232 │         if (updatedAt == 0 || answeredInRound < roundId || updatedAt > block.timestamp) revert StalePrice(feed);\n    │                                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:233:13\n    │\n233 │         if (block.timestamp - updatedAt > MAX_PRICE_AGE) revert StalePrice(feed);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/CallBook.t.sol:160:37\n    │\n160 │         emit Committed(1, alice, h, uint64(T0), expiry);\n    │                                     ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:265:13\n    │\n265 │         if (block.timestamp < c.expiry) revert NotExpired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:266:13\n    │\n266 │         if (block.timestamp > uint256(c.expiry) + REVEAL_WINDOW) revert RevealWindowClosed();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[unsafe-typecast]: typecasts that can truncate values should be checked\n    ╭▸ test/CallBook.t.sol:171:33\n    │\n171 │         assertEq(c.committedAt, uint64(T0));\n    │                                 ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:304:43\n    │\n304 │             if (msg.sender != c.caller && block.timestamp <= uint256(c.expiry) + REVEAL_WINDOW) {\n    │                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/CallBook.sol:326:13\n    │\n326 │         if (block.timestamp <= uint256(c.expiry) + REVEAL_WINDOW) revert RevealWindowOpen();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp\n\n","passed":true},{"durationMs":91,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/Deploy.t.sol:DeployTest\n[PASS] test_deployWiresConstructorArguments() (gas: 3540125)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 455.01µs (203.74µs CPU time)\n\nRan 6 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(uint256,address) (runs: 256, μ: 44838, ~: 44807)\n[PASS] test_metadataAndSupply() (gas: 20733)\n[PASS] test_noMintPath() (gas: 8779)\n[PASS] test_transfer() (gas: 46211)\n[PASS] test_transferFromRespectsAllowance() (gas: 72705)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 15416)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.89ms (4.03ms CPU time)\n\nRan 42 tests for test/CallBook.t.sol:CallBookTest\n[PASS] testFuzz_commit_expiryWindow(uint64) (runs: 256, μ: 133611, ~: 217952)\n[PASS] testFuzz_reveal_targetSideCheck(int256,int256,bool) (runs: 256, μ: 331191, ~: 310391)\n[PASS] testFuzz_settle_downHitIffAnswerReachesTarget(int256,int256,int256) (runs: 256, μ: 469480, ~: 453960)\n[PASS] testFuzz_settle_roundTimestampWindow(uint256) (runs: 256, μ: 406492, ~: 416906)\n[PASS] testFuzz_settle_upHitIffAnswerReachesTarget(int256,int256,int256) (runs: 256, μ: 471846, ~: 454173)\n[PASS] test_addFeed_boundedByMaxFeeds() (gas: 6140272)\n[PASS] test_addFeed_rejectsNonAggregator() (gas: 15936)\n[PASS] test_commit_expiryBounds() (gas: 347558)\n[PASS] test_commit_recordsPricesStatsAndEvent() (gas: 241897)\n[PASS] test_commit_rejectsEmptyHash() (gas: 11253)\n[PASS] test_commit_rejectsIncompleteLatestRound() (gas: 469988)\n[PASS] test_commit_rejectsNonPositiveAnswer() (gas: 188888)\n[PASS] test_commit_rejectsStaleFeed() (gas: 210359)\n[PASS] test_commit_skipsDisabledFeedAndNeedsAtLeastOne() (gas: 357102)\n[PASS] test_constructor_listsAndEnablesFeeds() (gas: 31915)\n[PASS] test_constructor_rejectsZeroAddresses() (gas: 77905)\n[PASS] test_contract_holdsNoEth() (gas: 17698)\n[PASS] test_feedDecimals_targetsUseTheFeedScale() (gas: 1130743)\n[PASS] test_hashCall_matchesOffchainEncoding() (gas: 11706)\n[PASS] test_markUnrevealed_forcesMissAfterWindow() (gas: 271154)\n[PASS] test_markUnrevealed_rejectsRevealedOrUnknown() (gas: 274510)\n[PASS] test_nonOwner_cannotManageFeeds() (gas: 15272)\n[PASS] test_owner_canAddAndDisableFeeds() (gas: 448130)\n[PASS] test_owner_disableRevertsWhenNotEnabled() (gas: 16633)\n[PASS] test_reveal_andSettleStillWorkAfterFeedDisabled() (gas: 426202)\n[PASS] test_reveal_beforeExpiry() (gas: 220737)\n[PASS] test_reveal_feedNotRecordedAtCommit() (gas: 750526)\n[PASS] test_reveal_happyPath() (gas: 279628)\n[PASS] test_reveal_hashMismatch() (gas: 233661)\n[PASS] test_reveal_late() (gas: 274329)\n[PASS] test_reveal_targetMustBeOnCorrectSide() (gas: 484185)\n[PASS] test_reveal_unknownOrSettledCall() (gas: 276041)\n[PASS] test_reveal_wrongCaller() (gas: 222533)\n[PASS] test_settle_hitAtExactTargetAndBoundaries() (gas: 865342)\n[PASS] test_settle_hitProofAcceptedAfterRevealWindow() (gas: 423521)\n[PASS] test_settle_hitProvenByIntermediateRound() (gas: 614841)\n[PASS] test_settle_missWithoutProof_byCaller() (gas: 309392)\n[PASS] test_settle_rejectsRoundsOutsideWindow() (gas: 376656)\n[PASS] test_settle_rejectsStaleAndIncompleteRounds() (gas: 529736)\n[PASS] test_settle_requiresRevealedAndSettlesOnce() (gas: 422040)\n[PASS] test_settle_thirdPartyMissOnlyAfterRevealWindow() (gas: 308228)\n[PASS] test_stats_streaksFollowSettlementOrder() (gas: 1295395)\nSuite result: ok. 42 passed; 0 failed; 0 skipped; finished in 19.98ms (83.24ms CPU time)\n\nRan 3 test suites in 20.91ms (24.33ms CPU time): 49 tests passed, 0 failed, 0 skipped (49 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"be386e9a16381c7726fe75e472e75c93b7e4cf1c4f52cfdc191f179ff0df5e46","verifiedTreeHash":"768f298706ba23df1adc1ab3728ffa84dbdc36b7","verifierVersion":"0.1.0+ff982c0f"}]}