{"assessments":[],"deployments":[{"attestationHash":"c401db344a55d6ca6bdbfb2b97b2ac6a14a95cc4e4ad380d307d3bdf1faa6499","chainId":11155111,"contracts":[{"address":"0xfe7929e6726350260b99aac04153c33173711272","blockNumber":11829798,"name":"LaunchToken","role":"token","txHash":"0x1067caea3b4203d2bb5d99f59b099caa32ea4d7870a70ea8887e36a7b253e9a3"},{"address":"0x5ac34dcae441a6ec30f5aa7aaa0a839afde7aa84","blockNumber":11829798,"name":"LumineonPriceFeed","role":"other","txHash":"0x1067caea3b4203d2bb5d99f59b099caa32ea4d7870a70ea8887e36a7b253e9a3"},{"address":"0x9f1511dc0e0d0c499b4bd50112b9f80a32fb5687","blockNumber":11829798,"name":"MerkleDistributor","role":"distributor","txHash":"0x1067caea3b4203d2bb5d99f59b099caa32ea4d7870a70ea8887e36a7b253e9a3"},{"address":"0x1b7dae02cbe9ccd80ae77e1f51884a324f006000","blockNumber":11829798,"name":"PoolInitializationGuard","role":"hook","txHash":"0x1067caea3b4203d2bb5d99f59b099caa32ea4d7870a70ea8887e36a7b253e9a3"}],"id":"87513a53-cb12-4e4e-9599-293a69a373ac","manifestHash":"9ef47a57d447b4aff7433eac6ad48f36ee3b277fc9e19f34876f8eff8aa3adb4","status":"live"}],"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":"1ce762a5-ffcc-4e9a-99db-ff6c977e8a9e","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"654f7798b9821118b5fe38d18ba10a8d20ea632ee7c706346ebe8187a23e2338","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":"23d5d703e3bf63d2f0fd6537e5c2e1774ccb19deb105ab382dc5a939b8ad1b55","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":"179a4599c7ebdede7bf1379a9efe8eba08b082c3bfaff7ae93d5ba722d1c4ef5","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"9c54f7a6e90555c05412896a599f97badb2b186b2248af852608d07324e5fc53","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":"d479cd4716ff5c36dd602380f3a7d80710e55c3872025895380bb7f4814d81b9","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":"9586fe6814a49d00f46a0052b40f965b80993049050dcdd9cfaeb247f753caeb","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":"4f4769f341033d9ff300477689e13a2006175d02678455c475b193a7a40b05b0","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"4789bf7f7821872a2ad26af372b68dc8e9b7b6df587cdf9dd5706e765bb5419a","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Build, test, independently review, and deploy a small Pokémon TCG price-oracle consumer contract on Ethereum Sepolia, chain ID 11155111, using IMD’s normal deployment pipeline.\n\nProject name: Lumineon Price Feed.\nApplication contract name: LumineonPriceFeed.\n\nThe purpose is to demonstrate that real IMD oracle attestations can update a card price onchain, where other contracts can read it. This is a testnet prototype.\n\n1. Fixed card and pricing definition\n\nThe feed represents exactly:\n- Lumineon V.\n- English Pokémon TCG, Crown Zenith: Galarian Gallery.\n- Card number GG39/GG70.\n- Grading company PSA, grade 10.\n- Currency USD, stored as an unsigned integer in cents.\n\nThe price means the headline price-guide estimate explicitly labelled “PSA 10” at:\nhttps://www.pricecharting.com/game/pokemon-crown-zenith/lumineon-v-gg39\n\nIt is a published benchmark observation, not an individual sale, asking price, guaranteed resale value, or average across sources. Exclude shipping, taxes, and fees. Expose the card identity, denomination, and source through readable contract metadata.\n\n2. Real IMD oracle integration\n\nFollow the current IMD oracle documentation:\nhttps://imd.fun/docs/\n\nUse the current official OracleAttestation.sol implementation supplied by IMD where available, with the version-2 EIP-712 schema. Verify compatibility against IMD’s actual attestation format; do not invent a substitute signing format.\n\nPin the trusted IMD attester from the supplied network configuration. Verify the signature against this deployed consumer’s address and Sepolia domain. Validate the signed chain context according to the protocol.\n\nBind accepted answers to the exact approved card-and-pricing question using IMD’s actual question-hashing rules. Explain how repeated requests remain compatible. Do not let the first submitter choose the trusted question or signer.\n\nRequire a panel of at least 20, quorum of at least 14, and agreed count meeting the signed quorum, with internally consistent counts. Accept only correctly encoded, positive uint256 prices in cents. Reject conflicting answer and figure values.\n\nAnyone may relay a valid answer, but nobody may set a price without a valid, authorized attestation.\n\n3. Updates and freshness\n\nStart with no price. Do not initialize the deployed contract with a hardcoded or simulated observation.\n\nOn a successful update, store the price, request ID, attestation issue time, expiry, onchain receipt time, and panel agreement information. Emit an event identifying the update.\n\nReject expired attestations, future-issued attestations, reused request IDs, and answers whose issue time is not strictly newer than the stored observation. Apply a maximum age of 24 hours from issuance.\n\nAllow a newly signed observation to refresh the feed even when its price is unchanged.\n\nProvide:\n- A reader exposing the last stored observation and whether it is fresh.\n- A strict price reader that reverts when there is no observation or it is stale.\n\nExpiry must not erase history. Never renew freshness merely because someone reads the feed. Label issuance time accurately; it is not the date of the underlying card sales.\n\n4. Deployment and scope\n\nKeep the application minimal and non-upgradeable, with no manual price override or custody of user funds. Configure deployment parameters through constructors compatible with IMD’s factory. If an administrative role is necessary for question binding, use the requester’s $owner configuration, never the deploying factory’s msg.sender, and document its powers.\n\nFollow IMD’s required launch-token rules if the selected deployment route requires a LaunchToken. The card-price feed must operate independently of that token and its pool.\n\nA website, watcher, recurring scheduler, automated relayer, and paid oracle requests are outside this assignment. Leave the deployed feed ready for us to submit real attestations afterward.\n\n5. Tests and delivery\n\nProvide meaningful Foundry tests for two successive valid updates, an unchanged-price refresh, unauthorized signers, wrong question, wrong domain/chain, insufficient agreement, malformed data, expiry, future timestamps, replay, out-of-order updates, and empty/stale reads. Include a small test consumer demonstrating another contract reading the price. Keep test signatures and fixtures separate from the deployed configuration.\n\nDeliver the source repository, passing test results, independent review, ABI, deployed Sepolia address, and deployment transaction.\n\nInclude a beginner-readable README and a reusable oracle-request template specifying the exact question, cents denomination, panel evidence, panel size 20, quorum 14, zero numeric tolerance, 24-hour validity, and the deployed consumer configuration.\n\nInclude a small utility that takes IMD’s actual attestation API response and prepares the contract update calldata. Document how to submit it with an external wallet; never request or embed private keys.\n\nClearly distinguish locally tested behavior from live verification. Do not claim the live price integration has succeeded until a real IMD attestation has actually been accepted onchain.\n\nToken name: lumineon\nToken symbol: lumi","parentJobId":null,"planHash":"3bc47884fd7ce9354b1250f5261bf1fc12d12b2f563b414398d4800b76a5f80c","previousHash":"65480bfb57da76aaa43bf2b7c81d219c30b293c1c4626b45122f18863f584898","projectId":"1ce762a5-ffcc-4e9a-99db-ff6c977e8a9e","publication":{"commit":"03bfe1653d2eeebc5b04d4945fb576328175aed9","deliveredAt":"2026-10-02T15:11:24.681Z","repoUrl":"https://github.com/identity-md-launches/launch-594-build-test-independently-review"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50959","feedbackHash":"1293795e00ceaa8c849fa8e19efe85752e973b6493d2ac77561d599465b4b15f","nodeKey":"write_foundry_tests","submissionHash":"4789bf7f7821872a2ad26af372b68dc8e9b7b6df587cdf9dd5706e765bb5419a","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"cd1e5803d0966ce67a52a6f3592a57e4940477eb35c3a26a048dba4d1e3e8efb","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"c7e970e4519848672561259681b14219a02831a15d06048768c701d24dfc50ef","device":"0256823ae36e7900","findings":[],"hash":"0d147fea8dacead35e2d36ce1f75409091764fc46bc0bfe795fe3dbdcf704815","nodeId":"847a8895-4d83-4b49-948a-9de830089620","outcome":"completed","summary":"Added adversarial, fuzz, and stateful invariant tests under `test/`.\n\n- `forge build` passes.\n- `forge test`: **91 passed, 0 failed, 0 skipped**.\n- Invariants exercised 40,960 randomized handler calls.\n- Independent review found no reproducible contract defects.\n\nCoverage notes are in `test/TESTING.md`. Live Sepolia deployment and real-attestation acceptance remain unverified.","treeHash":"6952e6abbdd049df12c3aca9493e1faa74fc3359","usage":{"cachedInputTokens":1203456,"inputTokens":90506,"model":"gpt-6-astra","outputTokens":12322,"runtime":"codex","turns":5,"wallClockMs":652676}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"720122d0ca9f60ca","findings":[],"hash":"179a4599c7ebdede7bf1379a9efe8eba08b082c3bfaff7ae93d5ba722d1c4ef5","nodeId":"bb4d68b6-b0dc-4bec-b331-db8e4b1fa2f1","outcome":"completed","summary":"Both prior findings are fixed. No new reproducible defects found.\n\nRecorded empty findings and all seven entry points in [.imd-findings.json](/Users/hhyy/.lobster_wallets/identitymd/work/1ce762a5-ffcc-4e9a-99db-ff6c977e8a9e/bb4d68b6-b0dc-4bec-b331-db8e4b1fa2f1/.imd-findings.json).\n\nValidation passed: 99 Foundry tests, 7 Node tests, formatting, and ABI comparisons. Live Sepolia deployment and attestation acceptance remain unverified.","treeHash":null,"usage":{"cachedInputTokens":1163008,"inputTokens":134816,"model":"gpt-6-astra","outputTokens":7348,"runtime":"codex","turns":5,"wallClockMs":658282}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"02ae6543274731ab","findings":[],"hash":"23d5d703e3bf63d2f0fd6537e5c2e1774ccb19deb105ab382dc5a939b8ad1b55","nodeId":"4ea97045-8d9c-449c-b807-a637ef658382","outcome":"completed","summary":"No substantiated defects in the assigned scope. Recorded all 7 entry points and review limits in [.imd-findings.json](.imd-findings.json).\n\nAll 67 Foundry tests passed. Source unchanged. Live Sepolia deployment and attestation acceptance remain unverified.","treeHash":null,"usage":{"cachedInputTokens":898560,"inputTokens":194914,"model":"gpt-6-astra","outputTokens":6813,"runtime":"codex","turns":5,"wallClockMs":399002}},{"artifacts":[],"attempt":3,"bundleHash":null,"device":"3f6a9bdd601cb99f","findings":[],"hash":"2aa7f3dea997e8dcd5f96a9aaf36486271ceab7033a0bf69ab38d60d78eefb96","nodeId":"539c72cb-8bc3-4a70-90e3-210a9edcf0e9","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":352}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0256823ae36e7900","findings":[],"hash":"2b6a13bb6ba15bb6196e10f89fb2c6938c2dce9772fcc7a0678c4e279f0afd98","nodeId":"539c72cb-8bc3-4a70-90e3-210a9edcf0e9","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":776}},{"artifacts":[],"attempt":1,"bundleHash":"abf998517e9c3947dfdfb7ab735102ae24c826c5ed7f721cba0527ffe230679d","device":"468e82a89b9bfe18","findings":[],"hash":"4789bf7f7821872a2ad26af372b68dc8e9b7b6df587cdf9dd5706e765bb5419a","nodeId":"847a8895-4d83-4b49-948a-9de830089620","outcome":"completed","summary":"The build was broken when I picked this up: the implementer's revision replaced hash-only `approveQuestion` with per-request `approveRequest(requestId, questionHash)`, and my adversarial and invariant suites still called the old API. I ported both to the new binding model and extended them to test it. The suite now compiles and passes.\n\n**Result**\n\n| Check | Outcome |\n|---|---|\n| `forge build --offline` | compiles |\n| `forge test --offline` | 97 passed, 0 failed, 0 skipped |\n| `forge fmt --check` | clean |\n\n**What changed** (only `test/`, three files):\n\n- `test/LumineonPriceFeed.adversarial.t.sol`. The fault table now approves each request before corrupting it, so every rejection is caused by the fault itself. It gained a twentieth case for an unapproved ID carrying the approved hash. Three tests were rewritten for the new model: replay laundering now covers the owner being refused a rebind of a consumed ID; cross-request signature reuse checks that approving two pairs never lets one signature authenticate the other; and the pending-owner test checks binding rights move to the accepted owner. Two new tests cover repeated requests sharing one hash, each needing its own approval and consumed once, and a 1,000-run fuzz of the exact-pair rule over random IDs, hashes and bit flips.\n- `test/LumineonPriceFeed.invariant.t.sol`. The handler's ghost state now tracks every binding the owner made, which are consumed, and IDs that must stay unbound. Invalid candidates are bound first so their intended fault is what rejects them, and bindings left behind by rejected relays are later consumed by valid submissions. The second invariant checks each binding is unchanged, consumed exactly when accepted, and that relaying never creates a binding. Still 256 runs of depth 64 with fail-on-revert.\n- `test/TESTING.md`. Updated counts and a revision note describing the ported coverage.\n\n**No findings reported.** The revised contract behaved as specified under every adversarial case, including the new binding rules, so I wrote no `.imd-findings.json`. Live Sepolia deployment and acceptance of a real consumer-bound IMD attestation remain unverified, as before.","treeHash":"9e532ac3ee8b2f62c00edd9a85b95e51d4167eae","usage":{"cachedInputTokens":1565552,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":25804,"runtime":"claude","turns":24,"wallClockMs":463798}},{"artifacts":[],"attempt":1,"bundleHash":"dfc6181e13f261595672ea45d576bc969ac016eba1317eb4f0397b346a197256","device":"468e82a89b9bfe18","findings":[],"hash":"4e8765ed9fbc71b9948ee3cb3246aa3f43058b4d432878c58e01f4633b16c6ec","nodeId":"262f4117-1338-4f57-b6e3-96108e55e038","outcome":"completed","summary":"Everything verified. The memory directory is denied by this session's permission settings, so no session notes were saved there; that does not affect the deliverable.\n\n## Recap\n\n**Delivered** a complete Foundry project for the Lumineon Price Feed in the repository root. A clean `forge build`, `forge test` (67 tests, including fuzz), `forge fmt --check` and an offline `forge script` dry run all pass with solc 0.8.26 pinned, `evm_version = \"paris\"`, `bytecode_hash = \"none\"`, and `forge-std` vendored as plain files.\n\n**Key findings that shaped the design**\n\n- The requester already ran a real IMD oracle request for this exact card question (request `ac82ce11…`, 20 seats, 14 agreed, $115.77). Its typed data gave me the live version-2 schema and the attester `0x5598Aa91…2982`. Because that request named no consumer, it was signed under IMD's default domain and can never update a contract. The README says so plainly.\n- IMD publishes no downloadable `OracleAttestation.sol`. I wrote `src/OracleAttestation.sol` from the documented schema and proved byte-compatibility by recovering IMD's real attester from its real signature in a test.\n- IMD's `questionHash` covers the whole pinned request, not the text. Two requests with identical text had different hashes, and roughly 450 candidate preimages did not reproduce the rule. The contract therefore binds the question through an owner-approved hash allowlist, with the owner taken from `$owner`, never the factory. The owner can only approve question hashes. It cannot set a price, revoke, pause or change the attester. This is documented as the trust assumption in README, REVIEW.md and docs/deployment.md.\n\n**What the feed enforces**: pinned attester over this contract's own Sepolia domain, approved question, `chainId` field 1, uint256 answer that is 32 bytes and matches `figure`, positive price, panel at least 20, quorum at least 14, agreed at least quorum and consistent counts, not future-issued, not expired, at most 24 hours old, unused request ID, strictly newer than the stored observation. Strict and lenient readers, history kept after expiry, reads never renew freshness.\n\n**Also included**: `LaunchToken` (lumineon / lumi, fixed 10^27 supply), ABI exports under `docs/abi/`, the reusable request template with panel 20, quorum 14, zero tolerance, 24-hour validity and the consumer domain, a dependency-free `tools/prepare-update.mjs` whose calldata and keccak match `cast` exactly, an example consumer contract with tests, a draft `launch.json`, and REVIEW.md with twelve dispositions.\n\n**Not done, by design of the pipeline**: no Sepolia address and no deployment transaction exist yet, because deployment is performed by IMD's deployer after review. No live attestation has been accepted on-chain, and the README states the live integration must not be called successful until a `PriceUpdated` event exists on Sepolia.","treeHash":"502b52e1a026206e0ac80f15ef77533585caa226","usage":{"cachedInputTokens":3018894,"inputTokens":834,"model":"claude-fable-5-1","outputTokens":88488,"runtime":"claude","turns":83,"wallClockMs":1090944}},{"artifacts":[],"attempt":1,"bundleHash":"217027ff6441127037eb98a7b0b3b0a7db8cc930cd7a74e7c07a91f6cb620ee6","device":"468e82a89b9bfe18","findings":[],"hash":"4f4769f341033d9ff300477689e13a2006175d02678455c475b193a7a40b05b0","nodeId":"539c72cb-8bc3-4a70-90e3-210a9edcf0e9","outcome":"completed","summary":"The manifest is written and verified. Only `launch.json` changed.\n\n**What I did**\n- Re-derived `launch.json` from the accepted tree. The token entry names `LaunchToken` with name `lumineon`, symbol `lumi`, 18 decimals, matching `src/LaunchToken.sol`.\n- The single application contract is `LumineonPriceFeed` with constructor arguments `$owner` and the IMD attester address. This matches the constructor in the source, the exported ABI under `docs/abi/`, and the deployment script's pinned constant.\n- Pool stays native ETH, fee 3000, tickSpacing 60, the standard sqrtPriceX96 initial price.\n- Replaced the build step's draft notes with final manifest notes describing the owner's exact powers, why the factory's msg.sender cannot be the owner, the attester provenance, every acceptance rule the feed enforces, the readers, and the post-deployment request flow. Notes are 3099 characters, within the limit.\n\n**Verification**\n- Schema check with a local script: no errors, unique names, references valid, no extra fields.\n- Offline build succeeded and all 71 tests passed, including fuzz suites.\n- Git diff touches only `launch.json`.\n\nThe two earlier failures were daemon clone errors, not content faults, so nothing in the manifest needed to change for that reason.","treeHash":"7080a5ee181d9b021f2573c9d46885f6238a6f7a","usage":{"cachedInputTokens":452854,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":5624,"runtime":"claude","turns":9,"wallClockMs":93993}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"98b4506bef931d13","findings":[{"citation":"resolved","description":"Area: Economic Security (dependency semantics), Invariant (approval <-> single pinned request), Flow Gap (periphery x first principles). The whole acceptance policy that is not in the signed message (zero numeric tolerance, guards, one answer per approval, owner control over which requests may update) rests on the assumption at lines 158-159 and in README/docs that an IMD questionHash identifies exactly one pinned request. I measured IMD's actual hashing rules with the public quote route (POST /requests/quote, which returns inputJson.questionHash and the pinned window before any payment). Result: questionHash = f(question text, chainId, pinned fromBlock/toBlock, answerType, evidence, definitions). It does NOT change when toleranceBps, guards, consumer, validForSeconds, panelSize or quorum change, and an explicit window {fromBlock,toBlock} equal to an existing request's pinned window reproduces that request's hash exactly (I reproduced the real request ac82ce11's hash 0x0716e25b...6e4b from the template text plus its public window 26097748-26104920, with and without consumer/guards/tolerance). The pinned window of every request is public at GET /oracle/requests/:id. Consequences on this contract: (1) once the owner approves hash H, any wallet can pay 0.5 IMD for a new request carrying H with consumer = this feed and toleranceBps 10000 (every pair of positive answers 'agrees'), no guards and any validForSeconds; IMD signs it with a fresh requestId and a newer issuedAt; submitAttestation at line 190 sees approvedQuestions[H] true and every other check (lines 191-218) passes because the fields the attacker relaxed are not signed and the signed ones (panelSize 20, quorum 14) can be kept at the floors; the observation is replaced. The template's zero-tolerance / guard policy the brief requires is therefore enforceable only for the owner's own request, not for the feed. (2) The NatSpec and README guarantee 'a mistaken approval admits at most that request's single signed answer' is false: it admits an unbounded number of answers from anyone, forever, and there is no revoke, so the owner cannot stop third-party-funded updates once a hash is approved. (3) The on-chain binding reduces to text + window + definitions + evidence, so the owner's approval checklist in docs/deployment.md step 3 (compare question text only) is insufficient: toleranceBps, guards and consumer must be checked per request, and even then they are not bound after approval. Not verified (would require a paid request): how IMD picks the signed figure when toleranceBps > 0 clusters divergent honest answers; with a fully honest panel the figure stays the page value, so I rate this medium (broken guarantee / policy bypass) rather than high. Minimal fix that preserves the design (owner approves, never sets a price): bind approvals to the request identity IMD actually makes unique, i.e. approve the bytes32 requestId (UUID left-aligned, known from the payment result and GET /oracle/requests/:id) together with its questionHash, and require both at submit (approvedRequests[a.requestId] && a.questionHash == approvedQuestionOf[a.requestId]); that restores one-approval-one-attestation and lets the owner verify toleranceBps/guards/consumer of that specific request before approving. Alternatively, or additionally, make approvals revocable. Update lines 158-159, README 'Repeated requests stay compatible' and docs/deployment.md accordingly.","line":158,"path":"src/LumineonPriceFeed.sol","reproduction":"Live measurement (2026-10-02, unpaid quotes via POST https://api.imd.fun/requests/quote with a self-generated bearer token; same explicit window {fromBlock:26104818,toBlock:26105117}, template text/definitions, consumer 0x1111...1111, toleranceBps 0): A base -> questionHash 0xee262f936ec2f6bdbdc1590b41ed8d109cba6f019842fe73eb03ea6581b1deff. Same hash 0xee262f93...deff for: B toleranceBps 10000; D consumer 0x2222...2222; E guards removed; F validForSeconds 3600; G panelSize 5 quorum 2; L guards.max '1'; M consumer.chainId 1. Different hash for: C definitions.units changed -> 0x029c0967...f0b5; H question text + one space -> 0x2a43ce98...40d6; I fromBlock-1 -> 0xbdf0a8db...f83f; J definitions removed -> 0x728453db...48f1; K evidence 'chain' -> 0x2be05ee8...c65e; chainId 56 -> 0x4687ad59...ff57; answerType bytes32 -> 0xfee8d675...cf54. Real-record check: template text + window {26097748,26104920} (ac82ce11's public pinned window), no definitions -> 0x0716e25b7e0736da38b3671c138b25da76141b849730204431cb796203226e4b, identical to the real request's questionHash in docs/examples/real-attestation-ac82ce11.json; adding consumer {11155111, 0x1111...1111} + template guards + toleranceBps 0 leaves it unchanged. On-chain sequence: (1) owner calls approveQuestion(H) for its template request R1 (toleranceBps 0). (2) Attacker reads R1's pinned window from GET /oracle/requests/R1, pays oracle.request with identical question/definitions/evidence/chainId/answerType, window = that explicit window, toleranceBps 10000, guards omitted, consumer = {11155111, feed}; IMD returns the same H and a new requestId R2. (3) Panel answers, IMD signs {requestId R2, questionHash H, panelSize 20, quorum 14, agreed n>=14, issuedAt newer}. (4) Anyone calls submitAttestation(R2 message, sig): line 190 approvedQuestions[H] true, lines 191-218 pass, line 223 overwrites the observation. Expected (NatSpec 158-159, README 'admits at most that request's single signed answer', template 'toleranceBps 0 ... exact agreement is required'): only R1's single answer under zero tolerance is admissible. Actual: an unbounded number of attestations from any payer under any tolerance/guards/validity are admissible under the one approval, and the owner has no way to stop them.","severity":"medium","snippet":"    /// @dev Approvals are permanent and cannot be revoked; each hash identifies one pinned request,\n    ///      so a mistaken approval admits at most that request's single signed answer.\n    function approveQuestion(bytes32 questionHash) external onlyOwner {","title":"Question approval binds far less than designed: IMD's questionHash omits toleranceBps, guards, consumer, validity and panel counts, and anyone can reproduce an approved hash by re-pinning its block wi"},{"citation":"resolved","description":"Flow gap between the tool (periphery) and the feed's domain check (line 217-218 of the contract). When --feed or --chain is omitted the defaults are taken from the attestation's own domain, so the comparisons at lines 139-140 (`Number(domain.chainId) !== chain`, `verifyingContract !== feed`) compare the domain with itself and can never fail; only the literal 0x0 default-domain case (line 141) is still caught. The README's usage line passes both flags, but the usage banner at line 123 and the help text show them as optional, and the operator runbook in docs/deployment.md step 4 shows only --feed (no --chain), which leaves the chain comparison vacuous. A relayer following the banner gets exit code 0, 'all offline checks pass', and a ready-to-run `cast send <other consumer> <calldata>` line for an attestation this feed rejects with InvalidSignature (and which, sent to the printed address, is a transaction to a contract the relayer did not intend). Fix: make --feed and --chain required (or default chain to 11155111 and the feed to nothing, and fail when the feed is missing) so the domain comparison is always against the operator's intended consumer.","line":130,"path":"tools/prepare-update.mjs","reproduction":"Take docs/examples/real-attestation-ac82ce11.json, set domain.verifyingContract to 0x2222222222222222222222222222222222222222 (an attestation IMD signed for some other consumer on chain 1), save as /tmp/other-consumer.json. Run `node tools/prepare-update.mjs /tmp/other-consumer.json --now 1790950000`: exit 0, output feed=0x2222...2222, chain=1, offlineChecks=['all offline checks pass'], and a `cast send 0x2222...2222 0x383f5938...` line. Expected: the tool must flag that the signed domain (chain 1, 0x2222...) is not the Lumineon feed on 11155111, as it does when both flags are passed (`--feed 0x1111...1111 --chain 11155111` -> exit 1, 'signed domain chainId 1 != consumer chain 11155111', 'signed verifyingContract 0x2222... != feed 0x1111...'). Sending the printed calldata to the Sepolia feed reverts InvalidSignature (same path as test_rejectsWrongVerifyingContract).","severity":"low","snippet":"  const feed = (opt(\"--feed\", domain?.verifyingContract) ?? \"\").toLowerCase();\n  const chain = Number(opt(\"--chain\", domain?.chainId ?? 11155111));","title":"prepare-update.mjs domain checks are vacuous without --feed/--chain: an attestation signed for another consumer prints 'all offline checks pass' and a cast send line aimed at the other contract"}],"hash":"654f7798b9821118b5fe38d18ba10a8d20ea632ee7c706346ebe8187a23e2338","nodeId":"3d9f31da-d421-4158-a28b-78e0d076cc17","outcome":"completed","summary":"Review complete. The findings file is written at the repository root with two findings and eleven coverage rows, and no other file in the tree was changed.\n\n**What I found**\n\n- **Medium, question binding.** The owner-approval scheme assumes an IMD `questionHash` identifies one pinned request. I measured IMD's hashing with its unpaid quote route. The hash covers question text, chain, pinned block window, answer type, evidence and definitions only. It ignores `toleranceBps`, `guards`, `consumer`, `validForSeconds`, `panelSize` and `quorum`. Anyone can reproduce an approved hash by passing the same explicit block window, which is public. I reproduced the real request's hash exactly from the template text and its window. So one permanent approval lets any wallet buy unlimited new requests under that hash with 100% tolerance and no guards, and the contract accepts each one at line 190 of `src/LumineonPriceFeed.sol`. The \"zero tolerance\" and \"one approval admits one answer\" guarantees are unenforceable and there is no revoke. Suggested fix: approve the request ID together with its question hash, which keeps the owner as approver only.\n- **Low, relay tool.** Without the `--feed` and `--chain` flags, the calldata tool compares the attestation's domain with itself, reports \"all offline checks pass\" for an attestation signed for another consumer, and prints a send command aimed at that other contract. The runbook in the deployment doc omits one flag.\n\n**What holds**\n\nAll signed-field checks, time boundaries, replay, ordering, encoding and signature paths in the relay function behave as intended. The launch token, ownership handover, reader consistency and the no-funds, no-forbidden-opcode properties hold. The tool's calldata is byte-identical to Foundry's encoding. The full suite passes offline.\n\n**Not verified**\n\nHow IMD picks the signed figure when tolerance clusters divergent honest answers would need a paid request. I rated the first finding medium on that basis rather than high.","treeHash":null,"usage":{"cachedInputTokens":1838847,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":35559,"runtime":"claude","turns":44,"wallClockMs":549687}},{"artifacts":[],"attempt":1,"bundleHash":"0028b36c105bcd73dd3fe43efe017a4eeb2cca11bb19de1a394bfc0f3c028089","device":"0256823ae36e7900","findings":[],"hash":"9586fe6814a49d00f46a0052b40f965b80993049050dcdd9cfaeb247f753caeb","nodeId":"262f4117-1338-4f57-b6e3-96108e55e038","outcome":"completed","summary":"Both findings reproduced and fixed:\n\n- Approval now binds each request ID to its question hash.\n- Calldata utility requires `--feed` and independently defaults to Sepolia.\n\nRecorded both responses in `.imd-responses.json`; updated ABI, documentation, and regression tests. Separate agent review found no remaining defects.\n\nValidation passed: `forge build`, 71 Foundry tests, `forge fmt --check`, and 7 utility tests.\n\nDeployment and real onchain attestation acceptance remain pending.","treeHash":"ba22276071c5cbf3fbab4e19c86c3aed5362af4a","usage":{"cachedInputTokens":1128704,"inputTokens":60687,"model":"gpt-6-astra","outputTokens":12355,"runtime":"codex","turns":5,"wallClockMs":982309}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"35c52a5b502e847c","findings":[],"hash":"9c54f7a6e90555c05412896a599f97badb2b186b2248af852608d07324e5fc53","nodeId":"d8e1f88c-90f3-4dae-aba8-0f136047df7a","outcome":"completed","summary":"Recorded no substantiated defects in [.imd-findings.json](/home/identitymd5/.identitymd/work/1ce762a5-ffcc-4e9a-99db-ff6c977e8a9e/d8e1f88c-90f3-4dae-aba8-0f136047df7a/.imd-findings.json).\n\n- Covered all seven entry points within the assigned numerical scope.\n- All 67 Foundry tests passed.\n- Calldata, hash-boundary and ABI checks passed.\n- Implementation unchanged; live deployment and relay remain unverified.","treeHash":null,"usage":{"cachedInputTokens":782208,"inputTokens":107761,"model":"gpt-6-astra","outputTokens":6458,"runtime":"codex","turns":5,"wallClockMs":296722}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"720122d0ca9f60ca","findings":[{"citation":"resolved","description":"approveQuestion permanently authorizes a questionHash, based on the stated assumption at lines 158-159 that it identifies one request. IMD permits distinct requests with the same pinned window, question, definitions and evidence to share that hash even when toleranceBps and guards differ. Those two policy fields are absent from the signed v2 message. An unprivileged payer can therefore reproduce an approved hash, request this consumer with nonzero tolerance and no guards, and relay a fresh IMD signature under the existing approval. The signed 20/14/agreed checks cannot establish zero-tolerance agreement. This breaks the promised per-request approval boundary and exact-agreement policy; requestId replay protection only stops reuse of the same ID, and the approval cannot be revoked. The effect does not require a malicious owner or forging IMD's signature. Bind approval to both the actual requestId and its questionHash, after the owner checks that specific request's text and settings. Preserve permissionless relay and allow new approved request IDs to refresh an unchanged price. This finding combines the specialist's policy-bypass and unlimited-approval claims, which have the same root cause.","line":190,"path":"src/LumineonPriceFeed.sol","reproduction":"Independently reproduced using unpaid POST https://api.imd.fun/requests/quote calls on 2026-10-02: use docs/oracle-request-template.json minus $comment, set consumer.verifyingContract to 0x1111111111111111111111111111111111111111, retain consumer.chainId=11155111, and set window={fromBlock:26104818,toBlock:26105117}. Submit {requestKey:<fresh UUID>,action:'oracle.request',input:<body>} with a fresh random bearer token. The base quote (order 71325e4a-ed05-41d1-b51e-69c272648336) returned questionHash H=0xee262f936ec2f6bdbdc1590b41ed8d109cba6f019842fe73eb03ea6581b1deff. Repeat with a new requestKey, toleranceBps=10000 and guards removed: order 81e021d3-82d0-40ef-81fc-ba8ef16b01d0 returned the identical H and explicitly retained toleranceBps=10000 in inputJson.request. Both returned HTTP 201 and paidAt=null. Concrete contract trace: deploy on chain 11155111 with owner O and attester S; O approves H for the zero-tolerance request. At block.timestamp=1790950700 relay an S-signed v2 attestation under this feed's domain with an unused requestId for the second request, questionHash=H, chainId=1, answerType=3, answer=abi.encode(uint256(11577)), figure=11577, fromBlock=26104818, toBlock=26105117, blockHash=0x8c333f150208331e02a9c905fbd7d9137e8ee9e2337e6a5ae34cdce6fbb999ff, its panelJobId, panelSize=20, quorum=14, agreed=14, issuedAt=1790950690, expiresAt=1791037090; begin with no observation or one issued earlier. Every guard at lines 190-218 passes and lines 221-235 store the second request although the owner approved only the first request's settings. Expected: reject a distinct request whose agreement policy was never approved. Actual: no request-ID approval or tolerance check exists. Existing passing test test_twoSuccessiveValidUpdates also executes two different signed request IDs under the single hash approved in FeedTestBase.setUp, demonstrating approval reuse locally. Live evidence here establishes the hash collision between policy settings; no paid request, live relaxed-policy attestation, or onchain exploit was executed.","severity":"medium","snippet":"        if (!approvedQuestions[a.questionHash]) revert QuestionNotApproved(a.questionHash);","title":"Question-hash approval also authorizes new requests with relaxed agreement settings"},{"citation":"resolved","description":"The usage banner makes --feed and --chain optional. Without them, the comparisons on lines 139-140 compare the response's domain with itself. A response for chain 1 and another nonzero consumer is labelled 'all offline checks pass', and the printed Sepolia cast commands target that other address. The deployment runbook passes --feed but omits --chain, leaving the wrong-chain case undetected even on that documented path. This can cause failed relays or an unintended transaction target; it does not bypass the contract's signature checks. Require an independently supplied feed address and default the expected chain to Sepolia (or require --chain), and align the runbook.","line":130,"path":"tools/prepare-update.mjs","reproduction":"Executed without writing a fixture: load docs/examples/real-attestation-ac82ce11.json, replace only domain.verifyingContract with 0x2222222222222222222222222222222222222222, and pass the JSON on stdin to `node tools/prepare-update.mjs /dev/stdin --now 1790950000`. Actual: exit 0, feed=0x2222222222222222222222222222222222222222, chain=1, offlineChecks=['all offline checks pass'], and cast send/call commands targeting 0x2222...2222 with <SEPOLIA_RPC>. Adding `--feed 0x1111111111111111111111111111111111111111 --chain 11155111` correctly exits 1 with both domain mismatches. Supplying only `--feed 0x2222222222222222222222222222222222222222` still exits 0 despite chain 1. Expected: require the intended consumer and reject a non-Sepolia domain independently of the response. The edited fixture demonstrates the metadata-validation defect only; its signature is not claimed to remain valid after editing.","severity":"low","snippet":"  const feed = (opt(\"--feed\", domain?.verifyingContract) ?? \"\").toLowerCase();\n  const chain = Number(opt(\"--chain\", domain?.chainId ?? 11155111));","title":"Calldata utility trusts the response's own domain when the intended consumer is omitted"}],"hash":"a1e22cc8a20be445a2db86ce990f7a48f4f8c4a8abc94c89caf37f7bc86153ae","nodeId":"bb4d68b6-b0dc-4bec-b331-db8e4b1fa2f1","outcome":"completed","summary":"Wrote [.imd-findings.json](/Users/hhyy/.lobster_wallets/identitymd/work/1ce762a5-ffcc-4e9a-99db-ff6c977e8a9e/bb4d68b6-b0dc-4bec-b331-db8e4b1fa2f1/.imd-findings.json).\n\n- **Medium:** Question-hash approval permits requests with relaxed agreement settings.\n- **Low:** Calldata-tool defaults fail to detect wrong domains.\n\nBoth include concrete reproductions. Coverage: **7/7 entry points**. All **93 Foundry tests passed**. Implementation unchanged; no live relay performed.","treeHash":null,"usage":{"cachedInputTokens":1294208,"inputTokens":113239,"model":"gpt-6-astra","outputTokens":9966,"runtime":"codex","turns":6,"wallClockMs":529093}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3f6a9bdd601cb99f","findings":[],"hash":"d479cd4716ff5c36dd602380f3a7d80710e55c3872025895380bb7f4814d81b9","nodeId":"4f383ff3-d0f4-4f91-b430-77cc309a80eb","outcome":"completed","summary":"Recorded [findings and coverage](.imd-findings.json): no substantiated defects in the assigned permission area; all 7 entry points covered.\n\nAll 67 Foundry tests passed. Limitations recorded: full question-hash derivation and live Sepolia deployment/relay remain unverified.","treeHash":null,"usage":{"cachedInputTokens":764800,"inputTokens":94695,"model":"gpt-6-astra","outputTokens":6805,"runtime":"codex","turns":5,"wallClockMs":354108}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"35c52a5b502e847c","findings":[],"hash":"dad83e52487f75127df29544966473c99b6b9762a263c586fb2531f40fcb7814","nodeId":"539c72cb-8bc3-4a70-90e3-210a9edcf0e9","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":589}},{"artifacts":[],"attempt":1,"bundleHash":"70e879732e424e4ada6203b1bd460d2711981e29bf558dca7d8eb81632c1a0fb","device":"468e82a89b9bfe18","findings":[],"hash":"f7ea25189bc8baed12118a6a3c370d6d80566e334165d14dfc727304ffee57fb","nodeId":"539c72cb-8bc3-4a70-90e3-210a9edcf0e9","outcome":"completed","summary":"launch.json is written and verified. The only modified file in the tree is the manifest; the untracked dotfiles in the status output are sandbox-mounted device nodes, not files I created.\n\n**What the manifest declares**\n\n- Token: LaunchToken, name lumineon, symbol lumi, 18 decimals, no constructor arguments, matching src/LaunchToken.sol.\n- One application contract, LumineonPriceFeed, with constructor arguments `$owner` and the IMD attester address. These match the accepted constructor signature `(address owner_, address attester_)` and the ABI export under docs/abi.\n- Pool: native ETH pairing, fee 3000, tickSpacing 60, the standard sqrtPriceX96 opening price.\n- Notes rewritten from the build step's draft into a final description: the owner's exact powers (question approval and two-step ownership transfer only, no price control), the immutable attester, the full list of checks submitAttestation enforces, the readers, and the statement that live verification is still pending until a real attestation lands onchain.\n\n**Verification**\n\n- The attester address was confirmed against the live IMD API today, which reports the same signer the accepted source and compatibility test pin.\n- Every check described in the notes was read back against the source of submitAttestation, and I corrected two naming slips in my first draft (the struct field is `agreed`, and the relay tool is tools/prepare-update.mjs).\n- A local script checked the manifest against the schema: kind, identifier patterns, pool fields, initialPrice bound, exactly the five required top-level keys, and notes length under 4000 characters.\n- Offline build and the full test run pass, 67 tests, 0 failures.\n\nNothing is left outstanding for this task. The feed deploys with no observation and waits for the owner to approve a real request's question hash before anyone can relay the first attestation.","treeHash":"ef4c452e333f55a2d6a5d1a01e3c345fd1903aba","usage":{"cachedInputTokens":505408,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":5960,"runtime":"claude","turns":11,"wallClockMs":90994}}],"verification":[{"checks":[{"durationMs":3909,"exitCode":0,"name":"build","output":"Compiling 38 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.78s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:210:13\n    │\n210 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:211:13\n    │\n211 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:212:13\n    │\n212 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:237:9\n    │\n237 │ ┏         emit PriceUpdated(\n238 │ ┃             a.requestId,\n239 │ ┃             a.questionHash,\n240 │ ┃             decoded,\n    ‡ ┃\n247 │ ┃             msg.sender\n248 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:222:29\n    │\n222 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:284:16\n    │\n284 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:312:13\n    │\n312 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:313:16\n    │\n313 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":6251,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4274410)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40433)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.86ms (937.37µs CPU time)\n\nRan 8 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_eachOfTheFifteenSignedFieldsIsAuthenticated() (gas: 332413)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12335)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1184)\n[PASS] test_realSignatureBindsEveryDomainComponent() (gas: 97002)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26544)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 104466)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 18.67ms (7.44ms CPU time)\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 291082)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52116)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 256478)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 34.18ms (2.34ms CPU time)\n\nRan 9 tests for test/LaunchToken.adversarial.t.sol:LaunchTokenAdversarialTest\n[PASS] testFuzz_failedDelegatedTransferRollsBackFiniteAllowance(uint256) (runs: 1000, μ: 199400, ~: 199197)\n[PASS] testFuzz_otherSpenderCannotUseAnApproval(uint256) (runs: 1000, μ: 123149, ~: 122880)\n[PASS] test_approvalReplacesRatherThanAddsAndCanBeRevoked() (gas: 162938)\n[PASS] test_delegatedSelfTransferConsumesAllowanceWithoutMovingBalance() (gas: 140176)\n[PASS] test_fullBalanceSelfTransferCannotCreateOrDestroyTokens() (gas: 48178)\n[PASS] test_maxUintTransferRevertsWithoutStateChange() (gas: 147912)\n[PASS] test_zeroAmountStillRejectsZeroRecipientAndZeroSpender() (gas: 98433)\n[PASS] test_zeroDelegatedTransferNeedsNoPositiveAllowance() (gas: 71502)\n[PASS] test_zeroOneAndFullSupplyTransfers() (gas: 234939)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 56.49ms (110.43ms CPU time)\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88509, ~: 88424)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 65.85ms (53.69ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 235682)\n[PASS] test_anyoneMayRelay() (gas: 232675)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 61852)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 243617)\n[PASS] test_metadataDescribesTheCard() (gas: 100852)\n[PASS] test_onlyOwnerApproves() (gas: 62240)\n[PASS] test_ownerApprovesNewQuestionThenRelayWorks() (gas: 317958)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83262)\n[PASS] test_readingNeverRenewsFreshness() (gas: 459925)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49276)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47966)\n[PASS] test_rejectsExpired() (gas: 50040)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49884)\n[PASS] test_rejectsFutureIssued() (gas: 49887)\n[PASS] test_rejectsGarbageSignature() (gas: 95112)\n[PASS] test_rejectsInconsistentCounts() (gas: 89050)\n[PASS] test_rejectsLowQuorum() (gas: 48285)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119360)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50488)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 328462)\n[PASS] test_rejectsReplay() (gas: 255110)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260756)\n[PASS] test_rejectsSmallPanel() (gas: 48105)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47147)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64023)\n[PASS] test_rejectsWrongAnswerType() (gas: 47402)\n[PASS] test_rejectsWrongDomainChain() (gas: 61178)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47140)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116425)\n[PASS] test_rejectsZeroPrice() (gas: 47237)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 350168)\n[PASS] test_startsEmpty() (gas: 53263)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61248)\n[PASS] test_twoStepOwnershipTransfer() (gas: 281946)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 402374)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 383085)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 66.72ms (11.64ms CPU time)\n\nRan 10 tests for test/LumineonPriceFeed.adversarial.t.sol:LumineonPriceFeedAdversarialTest\n[PASS] testFuzz_anyChangedSignatureByteCannotAuthenticate(uint256,uint8) (runs: 1000, μ: 188129, ~: 188297)\n[PASS] test_allNonUintAnswerTypesAreRejected() (gas: 45765590)\n[PASS] test_approvalAloneCannotAuthenticateASignatureForAnotherQuestion() (gas: 449819)\n[PASS] test_everyRejectionPreservesHistoryAndAllowsCorrectedRequest() (gas: 6116281)\n[PASS] test_highSMalleableSignatureCannotConsumeRequest() (gas: 395723)\n[PASS] test_oneCentAndMaximumUintPriceAreStoredWithoutNarrowing() (gas: 343319)\n[PASS] test_replacedPendingOwnerCannotTakeControl() (gas: 422730)\n[PASS] test_replayCannotBeLaunderedThroughAnotherApprovedQuestion() (gas: 430697)\n[PASS] test_signedExpiryDoesNotTurnReceiptTimeIntoIssuanceTime() (gas: 281778)\n[PASS] test_truncatedAndPaddedAnswerEncodingsAreRejected() (gas: 1366694)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 66.78ms (126.94ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 50058, ~: 49836)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 237374, ~: 237320)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 246868, ~: 244318)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 298370, ~: 326040)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 61665, ~: 61653)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 61443, ~: 61444)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 69321, ~: 53288)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 66.80ms (312.33ms CPU time)\n\nRan 2 tests for test/LumineonPriceFeed.invariant.t.sol:LumineonPriceFeedInvariantTest\n[PASS]\nLumineonPriceFeedInvariantTest invariants:\n[PASS] invariant_observationMatchesAcceptedInputsAndReadsDoNotRenewIt\n[PASS] invariant_onlyOwnerApprovesAndEveryAcceptedRequestRemainsConsumed\n LumineonPriceFeedInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+------------------------+-------+---------+----------╮\n| Contract            | Selector               | Calls | Reverts | Discards |\n+===========================================================================+\n| LumineonFeedHandler | acceptOwnership        | 2022  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | advanceTime            | 2063  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | approveQuestion        | 2016  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | replay                 | 2014  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | startOwnershipTransfer | 2097  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitInvalid          | 2084  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitOutOfOrder       | 2047  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitValid            | 2041  | 0       | 0        |\n╰---------------------+------------------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesRefreshExpiryReplayAndOwnership() (gas: 2845256)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.65s (4.65s CPU time)\n\nRan 1 test for test/LaunchToken.invariant.t.sol:LaunchTokenInvariantTest\n[PASS]\nLaunchTokenInvariantTest invariants:\n[PASS] invariant_allowancesMatchApprovalsAndSuccessfulSpends\n[PASS] invariant_fixedSupplyAndExactBalances\n LaunchTokenInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------------+----------------------+-------+---------+----------╮\n| Contract           | Selector             | Calls | Reverts | Discards |\n+========================================================================+\n| LaunchTokenHandler | approve              | 2764  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | approveZero          | 2692  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | overspendBalance     | 2765  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | revokeAndTrySpend    | 2670  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spend                | 2825  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spendMoreThanBalance | 2629  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spendToZero          | 2727  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | transfer             | 2773  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | transferToZero       | 2731  | 0       | 0        |\n╰--------------------+----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.15s (6.15s CPU time)\n\nRan 10 test suites in 6.16s (11.18s CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":64,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveQuestion(bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":240,\"REVIEW.md\":126,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":986,\"docs/deployment.md\":59,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":315,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":51,\"test/LaunchToken.adversarial.t.sol\":149,\"test/LaunchToken.invariant.t.sol\":182,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.adversarial.t.sol\":253,\"test/LumineonPriceFeed.fuzz.t.sol\":108,\"test/LumineonPriceFeed.invariant.t.sol\":280,\"test/LumineonPriceFeed.t.sol\":476,\"test/RealAttestationCompatibility.t.sol\":177,\"test/TESTING.md\":51,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":105,\"tools/prepare-update.mjs\":184},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0d147fea8dacead35e2d36ce1f75409091764fc46bc0bfe795fe3dbdcf704815","verifiedTreeHash":"6952e6abbdd049df12c3aca9493e1faa74fc3359","verifierVersion":"0.1.0+68ddf5e4"},{"checks":[{"durationMs":3372,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.24s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:213:13\n    │\n213 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:214:13\n    │\n214 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:215:13\n    │\n215 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:240:9\n    │\n240 │ ┏         emit PriceUpdated(\n241 │ ┃             a.requestId,\n242 │ ┃             a.questionHash,\n243 │ ┃             decoded,\n    ‡ ┃\n250 │ ┃             msg.sender\n251 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:225:29\n    │\n225 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:287:16\n    │\n287 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:315:13\n    │\n315 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:316:16\n    │\n316 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":5447,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/RequestApproval.t.sol:RequestApprovalTest\n[PASS] test_rejectedRequestCanRefreshUnchangedPriceAfterItsOwnApproval() (gas: 432114)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashAfterObservation() (gas: 279932)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashBeforeFirstObservation() (gas: 58786)\n[PASS] test_zeroHashCannotMatchUnapprovedRequestDefault() (gas: 156833)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.49ms (2.45ms CPU time)\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 344156)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52050)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 309626)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 20.17ms (1.34ms CPU time)\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4298011)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40631)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 57.71ms (5.16ms CPU time)\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88224, ~: 88466)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 68.40ms (12.18ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 287308)\n[PASS] test_anyoneMayRelay() (gas: 284323)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 122447)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 295243)\n[PASS] test_metadataDescribesTheCard() (gas: 100962)\n[PASS] test_onlyOwnerApproves() (gas: 62671)\n[PASS] test_ownerApprovesNewRequestThenRelayWorks() (gas: 318974)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83280)\n[PASS] test_readingNeverRenewsFreshness() (gas: 512783)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49316)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47984)\n[PASS] test_rejectsExpired() (gas: 50080)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49924)\n[PASS] test_rejectsFutureIssued() (gas: 49905)\n[PASS] test_rejectsGarbageSignature() (gas: 95148)\n[PASS] test_rejectsInconsistentCounts() (gas: 89108)\n[PASS] test_rejectsLowQuorum() (gas: 48325)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119436)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50506)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 429787)\n[PASS] test_rejectsReplay() (gas: 255157)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260803)\n[PASS] test_rejectsSmallPanel() (gas: 48145)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47297)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64063)\n[PASS] test_rejectsWrongAnswerType() (gas: 47333)\n[PASS] test_rejectsWrongDomainChain() (gas: 61108)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47158)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116461)\n[PASS] test_rejectsZeroPrice() (gas: 47277)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 401823)\n[PASS] test_startsEmpty() (gas: 53286)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61266)\n[PASS] test_twoStepOwnershipTransfer() (gas: 282592)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 454072)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 434586)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 121.53ms (9.29ms CPU time)\n\nRan 8 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_eachOfTheFifteenSignedFieldsIsAuthenticated() (gas: 332413)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12313)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1184)\n[PASS] test_realSignatureBindsEveryDomainComponent() (gas: 97002)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26544)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 105878)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 130.79ms (6.28ms CPU time)\n\nRan 12 tests for test/LumineonPriceFeed.adversarial.t.sol:LumineonPriceFeedAdversarialTest\n[PASS] testFuzz_anyChangedSignatureByteCannotAuthenticate(uint256,uint8) (runs: 1000, μ: 188283, ~: 188469)\n[PASS] testFuzz_onlyTheExactApprovedPairIsAdmitted(bytes32,bytes32,uint256) (runs: 1000, μ: 637948, ~: 638723)\n[PASS] test_allNonUintAnswerTypesAreRejected() (gas: 45795454)\n[PASS] test_approvalOfAPairCannotAuthenticateAnotherRequestsSignature() (gas: 750002)\n[PASS] test_everyRejectionPreservesHistoryAndAllowsCorrectedRequest() (gas: 7464984)\n[PASS] test_highSMalleableSignatureCannotConsumeRequest() (gas: 395880)\n[PASS] test_manyRequestsMayShareOneHashButEachNeedsItsOwnApprovalAndIsUsedOnce() (gas: 1976938)\n[PASS] test_oneCentAndMaximumUintPriceAreStoredWithoutNarrowing() (gas: 394943)\n[PASS] test_replacedPendingOwnerCannotTakeControl() (gas: 599178)\n[PASS] test_replayCannotBeLaunderedThroughRebindingOrAnotherHash() (gas: 587558)\n[PASS] test_signedExpiryDoesNotTurnReceiptTimeIntoIssuanceTime() (gas: 281786)\n[PASS] test_truncatedAndPaddedAnswerEncodingsAreRejected() (gas: 1367465)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 130.88ms (382.87ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 101664, ~: 101378)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 289037, ~: 288999)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 300608, ~: 297435)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 394647, ~: 370092)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 113313, ~: 113289)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 113102, ~: 113103)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 118819, ~: 104932)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 132.23ms (516.85ms CPU time)\n\nRan 9 tests for test/LaunchToken.adversarial.t.sol:LaunchTokenAdversarialTest\n[PASS] testFuzz_failedDelegatedTransferRollsBackFiniteAllowance(uint256) (runs: 1000, μ: 199415, ~: 199197)\n[PASS] testFuzz_otherSpenderCannotUseAnApproval(uint256) (runs: 1000, μ: 123150, ~: 122880)\n[PASS] test_approvalReplacesRatherThanAddsAndCanBeRevoked() (gas: 162938)\n[PASS] test_delegatedSelfTransferConsumesAllowanceWithoutMovingBalance() (gas: 140176)\n[PASS] test_fullBalanceSelfTransferCannotCreateOrDestroyTokens() (gas: 48178)\n[PASS] test_maxUintTransferRevertsWithoutStateChange() (gas: 147912)\n[PASS] test_zeroAmountStillRejectsZeroRecipientAndZeroSpender() (gas: 98433)\n[PASS] test_zeroDelegatedTransferNeedsNoPositiveAllowance() (gas: 71502)\n[PASS] test_zeroOneAndFullSupplyTransfers() (gas: 234939)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 132.38ms (217.59ms CPU time)\n\nRan 2 tests for test/LumineonPriceFeed.invariant.t.sol:LumineonPriceFeedInvariantTest\n[PASS]\nLumineonPriceFeedInvariantTest invariants:\n[PASS] invariant_bindingsArePermanentOwnerOnlyAndEachConsumedAtMostOnce\n[PASS] invariant_observationMatchesAcceptedInputsAndReadsDoNotRenewIt\n LumineonPriceFeedInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+------------------------+-------+---------+----------╮\n| Contract            | Selector               | Calls | Reverts | Discards |\n+===========================================================================+\n| LumineonFeedHandler | acceptOwnership        | 2021  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | advanceTime            | 2069  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | approveRequest         | 2032  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | replay                 | 2040  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | startOwnershipTransfer | 2061  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitInvalid          | 2026  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitOutOfOrder       | 2020  | 0       | 0        |\n|---------------------+------------------------+-------+---------+----------|\n| LumineonFeedHandler | submitValid            | 2115  | 0       | 0        |\n╰---------------------+------------------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesRefreshExpiryReplayBindingsAndOwnership() (gas: 4616298)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.96s (4.96s CPU time)\n\nRan 1 test for test/LaunchToken.invariant.t.sol:LaunchTokenInvariantTest\n[PASS]\nLaunchTokenInvariantTest invariants:\n[PASS] invariant_allowancesMatchApprovalsAndSuccessfulSpends\n[PASS] invariant_fixedSupplyAndExactBalances\n LaunchTokenInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------------+----------------------+-------+---------+----------╮\n| Contract           | Selector             | Calls | Reverts | Discards |\n+========================================================================+\n| LaunchTokenHandler | approve              | 2709  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | approveZero          | 2748  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | overspendBalance     | 2643  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | revokeAndTrySpend    | 2773  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spend                | 2774  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spendMoreThanBalance | 2689  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | spendToZero          | 2729  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | transfer             | 2750  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| LaunchTokenHandler | transferToZero       | 2761  | 0       | 0        |\n╰--------------------+----------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.36s (5.36s CPU time)\n\nRan 11 test suites in 5.36s (11.11s CPU time): 97 tests passed, 0 failed, 0 skipped (97 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveRequest(bytes32,bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":260,\"REVIEW.md\":179,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":1002,\"docs/deployment.md\":80,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":318,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":53,\"test/LaunchToken.adversarial.t.sol\":149,\"test/LaunchToken.invariant.t.sol\":182,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.adversarial.t.sol\":371,\"test/LumineonPriceFeed.fuzz.t.sol\":116,\"test/LumineonPriceFeed.invariant.t.sol\":385,\"test/LumineonPriceFeed.t.sol\":495,\"test/RealAttestationCompatibility.t.sol\":177,\"test/RequestApproval.t.sol\":59,\"test/TESTING.md\":58,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":111,\"tools/prepare-update.mjs\":189,\"tools/prepare-update.test.mjs\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4789bf7f7821872a2ad26af372b68dc8e9b7b6df587cdf9dd5706e765bb5419a","verifiedTreeHash":"9e532ac3ee8b2f62c00edd9a85b95e51d4167eae","verifierVersion":"0.1.0+68ddf5e4"},{"checks":[{"durationMs":1883,"exitCode":0,"name":"build","output":"Compiling 34 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.75s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:210:13\n    │\n210 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:211:13\n    │\n211 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:212:13\n    │\n212 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:237:9\n    │\n237 │ ┏         emit PriceUpdated(\n238 │ ┃             a.requestId,\n239 │ ┃             a.questionHash,\n240 │ ┃             decoded,\n    ‡ ┃\n247 │ ┃             msg.sender\n248 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:222:29\n    │\n222 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:284:16\n    │\n284 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:312:13\n    │\n312 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:313:16\n    │\n313 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":121,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 291082)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52116)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 256478)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.86ms (1.47ms CPU time)\n\nRan 6 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12313)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1095)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26522)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 104444)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 22.21ms (2.53ms CPU time)\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88482, ~: 88496)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 22.28ms (8.47ms CPU time)\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4274410)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40433)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 22.35ms (2.58ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 235682)\n[PASS] test_anyoneMayRelay() (gas: 232675)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 61852)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 243617)\n[PASS] test_metadataDescribesTheCard() (gas: 100852)\n[PASS] test_onlyOwnerApproves() (gas: 62240)\n[PASS] test_ownerApprovesNewQuestionThenRelayWorks() (gas: 317958)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83262)\n[PASS] test_readingNeverRenewsFreshness() (gas: 459925)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49276)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47966)\n[PASS] test_rejectsExpired() (gas: 50040)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49884)\n[PASS] test_rejectsFutureIssued() (gas: 49887)\n[PASS] test_rejectsGarbageSignature() (gas: 95112)\n[PASS] test_rejectsInconsistentCounts() (gas: 89050)\n[PASS] test_rejectsLowQuorum() (gas: 48285)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119360)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50488)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 328462)\n[PASS] test_rejectsReplay() (gas: 255110)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260756)\n[PASS] test_rejectsSmallPanel() (gas: 48105)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47147)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64023)\n[PASS] test_rejectsWrongAnswerType() (gas: 47402)\n[PASS] test_rejectsWrongDomainChain() (gas: 61178)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47140)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116425)\n[PASS] test_rejectsZeroPrice() (gas: 47237)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 350168)\n[PASS] test_startsEmpty() (gas: 53263)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61248)\n[PASS] test_twoStepOwnershipTransfer() (gas: 281946)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 402374)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 383085)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 23.61ms (14.18ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 50076, ~: 49743)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 237331, ~: 237287)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 247555, ~: 244318)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 294612, ~: 267176)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 61684, ~: 61641)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 61443, ~: 61440)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 66432, ~: 52963)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 29.66ms (137.10ms CPU time)\n\nRan 6 test suites in 30.29ms (121.98ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":48,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveQuestion(bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":240,\"REVIEW.md\":126,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":986,\"docs/deployment.md\":59,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":315,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":51,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.fuzz.t.sol\":108,\"test/LumineonPriceFeed.t.sol\":476,\"test/RealAttestationCompatibility.t.sol\":122,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":105,\"tools/prepare-update.mjs\":184},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":584,"exitCode":0,"name":"slither","output":"[low/medium] timestamp at src/LumineonPriceFeed.sol:282: LumineonPriceFeed.observationAge() (src/LumineonPriceFeed.sol#282-285) uses timestamp for comparisons\n[low/medium] timestamp at src/LumineonPriceFeed.sol:310: LumineonPriceFeed._isFresh() (src/LumineonPriceFeed.sol#310-314) uses timestamp for comparisons\n[low/medium] timestamp at src/LumineonPriceFeed.sol:188: LumineonPriceFeed.submitAttestation(OracleAttestation.Attestation,bytes) (src/LumineonPriceFeed.sol#188-249) uses timestamp for comparisons","passed":true},{"durationMs":304,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/LumineonPriceFeed.sol:160: Centralization Risk (2 places)\n[low] ecrecover at src/OracleAttestation.sol:115: `ecrecover` Signature Malleability\n[low] internal-function-used-once at src/OracleAttestation.sol:75: Internal Function Used Only Once\n[low] large-numeric-literal at src/LaunchToken.sol:13: Large Numeric Literal\n[low] missing-inheritance at src/LumineonPriceFeed.sol:26: Missing Inheritance","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4e8765ed9fbc71b9948ee3cb3246aa3f43058b4d432878c58e01f4633b16c6ec","verifiedTreeHash":"502b52e1a026206e0ac80f15ef77533585caa226","verifierVersion":"0.1.0+68ddf5e4"},{"checks":[{"durationMs":2061,"exitCode":0,"name":"build","output":"Compiling 35 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.93s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:213:13\n    │\n213 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:214:13\n    │\n214 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:215:13\n    │\n215 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:240:9\n    │\n240 │ ┏         emit PriceUpdated(\n241 │ ┃             a.requestId,\n242 │ ┃             a.questionHash,\n243 │ ┃             decoded,\n    ‡ ┃\n250 │ ┃             msg.sender\n251 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:225:29\n    │\n225 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:287:16\n    │\n287 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:315:13\n    │\n315 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:316:16\n    │\n316 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":114,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/RequestApproval.t.sol:RequestApprovalTest\n[PASS] test_rejectedRequestCanRefreshUnchangedPriceAfterItsOwnApproval() (gas: 432114)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashAfterObservation() (gas: 279932)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashBeforeFirstObservation() (gas: 58786)\n[PASS] test_zeroHashCannotMatchUnapprovedRequestDefault() (gas: 156833)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.15ms (7.02ms CPU time)\n\nRan 6 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12291)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1095)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26522)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 105856)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 22.60ms (10.22ms CPU time)\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4298011)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40631)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 27.17ms (1.08ms CPU time)\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88168, ~: 88478)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 27.10ms (7.65ms CPU time)\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 344156)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52050)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 309626)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 28.30ms (7.43ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 287308)\n[PASS] test_anyoneMayRelay() (gas: 284323)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 122447)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 295243)\n[PASS] test_metadataDescribesTheCard() (gas: 100962)\n[PASS] test_onlyOwnerApproves() (gas: 62671)\n[PASS] test_ownerApprovesNewRequestThenRelayWorks() (gas: 318974)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83280)\n[PASS] test_readingNeverRenewsFreshness() (gas: 512783)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49316)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47984)\n[PASS] test_rejectsExpired() (gas: 50080)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49924)\n[PASS] test_rejectsFutureIssued() (gas: 49905)\n[PASS] test_rejectsGarbageSignature() (gas: 95148)\n[PASS] test_rejectsInconsistentCounts() (gas: 89108)\n[PASS] test_rejectsLowQuorum() (gas: 48325)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119436)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50506)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 429787)\n[PASS] test_rejectsReplay() (gas: 255157)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260803)\n[PASS] test_rejectsSmallPanel() (gas: 48145)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47297)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64063)\n[PASS] test_rejectsWrongAnswerType() (gas: 47333)\n[PASS] test_rejectsWrongDomainChain() (gas: 61108)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47158)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116461)\n[PASS] test_rejectsZeroPrice() (gas: 47277)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 401823)\n[PASS] test_startsEmpty() (gas: 53286)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61266)\n[PASS] test_twoStepOwnershipTransfer() (gas: 282592)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 454072)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 434586)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 28.66ms (50.47ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 101670, ~: 101432)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 288987, ~: 288900)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 300666, ~: 297435)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 402681, ~: 428967)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 113328, ~: 113297)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 113102, ~: 113095)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 117371, ~: 104920)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 33.46ms (160.90ms CPU time)\n\nRan 7 test suites in 34.20ms (171.45ms CPU time): 71 tests passed, 0 failed, 0 skipped (71 total tests)\n","passed":true},{"durationMs":51,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveRequest(bytes32,bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":260,\"REVIEW.md\":179,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":1002,\"docs/deployment.md\":80,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":318,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":53,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.fuzz.t.sol\":116,\"test/LumineonPriceFeed.t.sol\":495,\"test/RealAttestationCompatibility.t.sol\":122,\"test/RequestApproval.t.sol\":59,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":111,\"tools/prepare-update.mjs\":189,\"tools/prepare-update.test.mjs\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4f4769f341033d9ff300477689e13a2006175d02678455c475b193a7a40b05b0","verifiedTreeHash":"7080a5ee181d9b021f2573c9d46885f6238a6f7a","verifierVersion":"0.1.0+68ddf5e4"},{"checks":[{"durationMs":2051,"exitCode":0,"name":"build","output":"Compiling 35 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.94s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:213:13\n    │\n213 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:214:13\n    │\n214 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:215:13\n    │\n215 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:240:9\n    │\n240 │ ┏         emit PriceUpdated(\n241 │ ┃             a.requestId,\n242 │ ┃             a.questionHash,\n243 │ ┃             decoded,\n    ‡ ┃\n250 │ ┃             msg.sender\n251 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:225:29\n    │\n225 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:287:16\n    │\n287 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:315:13\n    │\n315 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:316:16\n    │\n316 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":119,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/RequestApproval.t.sol:RequestApprovalTest\n[PASS] test_rejectedRequestCanRefreshUnchangedPriceAfterItsOwnApproval() (gas: 432114)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashAfterObservation() (gas: 279932)\n[PASS] test_rejectsUnapprovedRequestWithApprovedHashBeforeFirstObservation() (gas: 58786)\n[PASS] test_zeroHashCannotMatchUnapprovedRequestDefault() (gas: 156833)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 14.17ms (2.51ms CPU time)\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4298011)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40631)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 25.27ms (1.08ms CPU time)\n\nRan 6 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12291)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1095)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26522)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 105856)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 30.09ms (1.20ms CPU time)\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 344156)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52050)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 309626)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 30.97ms (1.01ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 287308)\n[PASS] test_anyoneMayRelay() (gas: 284323)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 122447)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 295243)\n[PASS] test_metadataDescribesTheCard() (gas: 100962)\n[PASS] test_onlyOwnerApproves() (gas: 62671)\n[PASS] test_ownerApprovesNewRequestThenRelayWorks() (gas: 318974)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83280)\n[PASS] test_readingNeverRenewsFreshness() (gas: 512783)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49316)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47984)\n[PASS] test_rejectsExpired() (gas: 50080)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49924)\n[PASS] test_rejectsFutureIssued() (gas: 49905)\n[PASS] test_rejectsGarbageSignature() (gas: 95148)\n[PASS] test_rejectsInconsistentCounts() (gas: 89108)\n[PASS] test_rejectsLowQuorum() (gas: 48325)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119436)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50506)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 429787)\n[PASS] test_rejectsReplay() (gas: 255157)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260803)\n[PASS] test_rejectsSmallPanel() (gas: 48145)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47297)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64063)\n[PASS] test_rejectsWrongAnswerType() (gas: 47333)\n[PASS] test_rejectsWrongDomainChain() (gas: 61108)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47158)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116461)\n[PASS] test_rejectsZeroPrice() (gas: 47277)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 401823)\n[PASS] test_startsEmpty() (gas: 53286)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61266)\n[PASS] test_twoStepOwnershipTransfer() (gas: 282592)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 454072)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 434586)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 31.06ms (10.70ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 101633, ~: 101378)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 288998, ~: 288954)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 301338, ~: 297435)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 399413, ~: 399539)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 113321, ~: 113297)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 113102, ~: 113103)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 115938, ~: 104908)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 31.06ms (166.84ms CPU time)\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88420, ~: 88448)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 31.02ms (3.88ms CPU time)\n\nRan 7 test suites in 31.91ms (193.64ms CPU time): 71 tests passed, 0 failed, 0 skipped (71 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveRequest(bytes32,bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":260,\"REVIEW.md\":179,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":1002,\"docs/deployment.md\":80,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":318,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":53,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.fuzz.t.sol\":116,\"test/LumineonPriceFeed.t.sol\":495,\"test/RealAttestationCompatibility.t.sol\":122,\"test/RequestApproval.t.sol\":59,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":111,\"tools/prepare-update.mjs\":189,\"tools/prepare-update.test.mjs\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":483,"exitCode":0,"name":"slither","output":"[low/medium] timestamp at src/LumineonPriceFeed.sol:313: LumineonPriceFeed._isFresh() (src/LumineonPriceFeed.sol#313-317) uses timestamp for comparisons\n[low/medium] timestamp at src/LumineonPriceFeed.sol:285: LumineonPriceFeed.observationAge() (src/LumineonPriceFeed.sol#285-288) uses timestamp for comparisons\n[low/medium] timestamp at src/LumineonPriceFeed.sol:188: LumineonPriceFeed.submitAttestation(OracleAttestation.Attestation,bytes) (src/LumineonPriceFeed.sol#188-252) uses timestamp for comparisons","passed":true},{"durationMs":246,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/LumineonPriceFeed.sol:160: Centralization Risk (2 places)\n[low] ecrecover at src/OracleAttestation.sol:115: `ecrecover` Signature Malleability\n[low] internal-function-used-once at src/OracleAttestation.sol:75: Internal Function Used Only Once\n[low] large-numeric-literal at src/LaunchToken.sol:13: Large Numeric Literal\n[low] missing-inheritance at src/LumineonPriceFeed.sol:25: Missing Inheritance","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9586fe6814a49d00f46a0052b40f965b80993049050dcdd9cfaeb247f753caeb","verifiedTreeHash":"ba22276071c5cbf3fbab4e19c86c3aed5362af4a","verifierVersion":"0.1.0+68ddf5e4"},{"checks":[{"durationMs":1947,"exitCode":0,"name":"build","output":"Compiling 34 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.82s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:210:13\n    │\n210 │         if (a.issuedAt > block.timestamp) revert IssuedInFuture(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:211:13\n    │\n211 │         if (block.timestamp > a.expiresAt) revert AttestationExpired(a.expiresAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:212:13\n    │\n212 │         if (block.timestamp - a.issuedAt > MAX_AGE) revert AttestationTooOld(a.issuedAt, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/LumineonPriceFeed.sol:237:9\n    │\n237 │ ┏         emit PriceUpdated(\n238 │ ┃             a.requestId,\n239 │ ┃             a.questionHash,\n240 │ ┃             decoded,\n    ‡ ┃\n247 │ ┃             msg.sender\n248 │ ┃         );\n    │ ┗━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/LumineonPriceFeed.sol:222:29\n    │\n222 │         uint64 receivedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:284:16\n    │\n284 │         return block.timestamp > _last.issuedAt ? block.timestamp - _last.issuedAt : 0;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:312:13\n    │\n312 │         if (block.timestamp > _last.expiresAt) return false;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/LumineonPriceFeed.sol:313:16\n    │\n313 │         return block.timestamp - _last.issuedAt <= MAX_AGE;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":110,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 9 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 88470, ~: 88472)\n[PASS] test_approveAndTransferFrom() (gas: 171819)\n[PASS] test_fixedSupplyMintedToDeployer() (gas: 25818)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 119817)\n[PASS] test_metadata() (gas: 24685)\n[PASS] test_noMintOrAdminSurface() (gas: 147328)\n[PASS] test_transferMovesExactAmount() (gas: 84460)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37054)\n[PASS] test_transferToZeroReverts() (gas: 32487)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 13.62ms (6.20ms CPU time)\n\nRan 4 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_attesterConstantIsTheLiveImdSigner() (gas: 326)\n[PASS] test_constructorRejectsZeroConfiguration() (gas: 4043)\n[PASS] test_deployFunctionProducesConfiguredContracts() (gas: 4274410)\n[PASS] test_factoryStyleDeploymentDoesNotGrantFactoryAnyRole() (gas: 40433)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 15.59ms (1.50ms CPU time)\n\nRan 3 tests for test/ExampleConsumer.t.sol:ExampleConsumerTest\n[PASS] test_consumerReadsFreshPrice() (gas: 291082)\n[PASS] test_strictReadRevertsWhenEmpty() (gas: 52116)\n[PASS] test_strictReadRevertsWhenStaleButLenientStillReports() (gas: 256478)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 15.90ms (1.04ms CPU time)\n\nRan 6 tests for test/RealAttestationCompatibility.t.sol:RealAttestationCompatibilityTest\n[PASS] test_anyFieldChangeBreaksRecovery() (gas: 61151)\n[PASS] test_feedDomainSeparatorMatchesLibrary() (gas: 12313)\n[PASS] test_realAnswerDecodesToFigure() (gas: 1095)\n[PASS] test_recoverRejectsMalformedSignatures() (gas: 35947)\n[PASS] test_recoversImdAttesterFromRealSignature() (gas: 26522)\n[PASS] test_sepoliaFeedRefusesDefaultDomainAttestation() (gas: 104444)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.29ms (1.89ms CPU time)\n\nRan 38 tests for test/LumineonPriceFeed.t.sol:LumineonPriceFeedTest\n[PASS] test_acceptsAtExactMaxAgeAndAtExpiry() (gas: 235682)\n[PASS] test_anyoneMayRelay() (gas: 232675)\n[PASS] test_approveRejectsZeroAndDuplicates() (gas: 61852)\n[PASS] test_feedHoldsNoEthAndRejectsIt() (gas: 33145)\n[PASS] test_freshnessHonoursSignedExpiryBeforeMaxAge() (gas: 243617)\n[PASS] test_metadataDescribesTheCard() (gas: 100852)\n[PASS] test_onlyOwnerApproves() (gas: 62240)\n[PASS] test_ownerApprovesNewQuestionThenRelayWorks() (gas: 317958)\n[PASS] test_ownerCannotSetPriceOrForgeAttestation() (gas: 83262)\n[PASS] test_readingNeverRenewsFreshness() (gas: 459925)\n[PASS] test_rejectsAgreedBelowQuorum() (gas: 49276)\n[PASS] test_rejectsAnswerFigureConflict() (gas: 47966)\n[PASS] test_rejectsExpired() (gas: 50040)\n[PASS] test_rejectsExpiryBeforeIssue() (gas: 49884)\n[PASS] test_rejectsFutureIssued() (gas: 49887)\n[PASS] test_rejectsGarbageSignature() (gas: 95112)\n[PASS] test_rejectsInconsistentCounts() (gas: 89050)\n[PASS] test_rejectsLowQuorum() (gas: 48285)\n[PASS] test_rejectsMalformedAnswerBytes() (gas: 119360)\n[PASS] test_rejectsOlderThanMaxAgeEvenIfUnexpired() (gas: 50488)\n[PASS] test_rejectsOutOfOrderOlderAttestation() (gas: 328462)\n[PASS] test_rejectsReplay() (gas: 255110)\n[PASS] test_rejectsReusedRequestIdWithNewerIssueTime() (gas: 260756)\n[PASS] test_rejectsSmallPanel() (gas: 48105)\n[PASS] test_rejectsUnapprovedQuestion() (gas: 47147)\n[PASS] test_rejectsUnauthorizedSigner() (gas: 64023)\n[PASS] test_rejectsWrongAnswerType() (gas: 47402)\n[PASS] test_rejectsWrongDomainChain() (gas: 61178)\n[PASS] test_rejectsWrongQuestionChainField() (gas: 47140)\n[PASS] test_rejectsWrongVerifyingContract() (gas: 116425)\n[PASS] test_rejectsZeroPrice() (gas: 47237)\n[PASS] test_staleFeedStillAcceptsNewerAttestation() (gas: 350168)\n[PASS] test_startsEmpty() (gas: 53263)\n[PASS] test_submitSelectorMatchesTooling() (gas: 360)\n[PASS] test_tamperedFieldAfterSigningIsRejected() (gas: 61248)\n[PASS] test_twoStepOwnershipTransfer() (gas: 281946)\n[PASS] test_twoSuccessiveValidUpdates() (gas: 402374)\n[PASS] test_unchangedPriceRefreshesFreshness() (gas: 383085)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 18.97ms (12.06ms CPU time)\n\nRan 7 tests for test/LumineonPriceFeed.fuzz.t.sol:LumineonPriceFeedFuzzTest\n[PASS] testFuzz_answerBytesMustEncodeFigureExactly(bytes,uint256) (runs: 256, μ: 50106, ~: 49893)\n[PASS] testFuzz_anyPositivePriceWithinWindowIsStored(uint256,uint32,uint64) (runs: 256, μ: 237334, ~: 237291)\n[PASS] testFuzz_freshnessIsPureFunctionOfTime(uint32) (runs: 256, μ: 247544, ~: 244318)\n[PASS] testFuzz_issueTimeMustStrictlyIncrease(uint32,uint32) (runs: 256, μ: 297662, ~: 326030)\n[PASS] testFuzz_onlyTheConfiguredAttesterIsAccepted(uint256) (runs: 256, μ: 61660, ~: 61637)\n[PASS] testFuzz_otherDomainsAreRejected(uint256,address) (runs: 256, μ: 61445, ~: 61444)\n[PASS] testFuzz_panelCountsBelowFloorsAreRejected(uint16,uint16,uint16) (runs: 256, μ: 66398, ~: 52675)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 25.01ms (111.49ms CPU time)\n\nRan 6 test suites in 25.56ms (106.38ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":35,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"LumineonPriceFeed.acceptOwnership()\",\"LumineonPriceFeed.approveQuestion(bytes32)\",\"LumineonPriceFeed.submitAttestation((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"LumineonPriceFeed.transferOwnership(address)\"],\"files\":{\".gitignore\":6,\"README.md\":240,\"REVIEW.md\":126,\"docs/abi/ILumineonPriceFeed.json\":142,\"docs/abi/LaunchToken.json\":262,\"docs/abi/LumineonPriceFeed.json\":986,\"docs/deployment.md\":59,\"docs/examples/real-attestation-ac82ce11.json\":1,\"docs/oracle-request-template.json\":30,\"foundry.toml\":20,\"launch.json\":25,\"remappings.txt\":1,\"script/Deploy.s.sol\":53,\"src/LaunchToken.sol\":67,\"src/LumineonPriceFeed.sol\":315,\"src/OracleAttestation.sol\":117,\"src/interfaces/ILumineonPriceFeed.sol\":29,\"test/Deploy.t.sol\":49,\"test/ExampleConsumer.t.sol\":51,\"test/LaunchToken.t.sol\":97,\"test/LumineonPriceFeed.fuzz.t.sol\":108,\"test/LumineonPriceFeed.t.sol\":476,\"test/RealAttestationCompatibility.t.sol\":122,\"test/helpers/AttestationHarness.sol\":23,\"test/helpers/ExampleConsumer.sol\":34,\"test/helpers/FeedTestBase.sol\":105,\"tools/prepare-update.mjs\":184},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f7ea25189bc8baed12118a6a3c370d6d80566e334165d14dfc727304ffee57fb","verifiedTreeHash":"ef4c452e333f55a2d6a5d1a01e3c345fd1903aba","verifierVersion":"0.1.0+68ddf5e4"}]}