{"assessments":[],"deployments":[{"attestationHash":"6d390490a809ef88708d13518b2b605e93f1f28541521cdc313454099d48a38a","chainId":4663,"contracts":[{"address":"0x3c81384aa1ca064316576a214e2fb30969992772","blockNumber":84236520,"name":"PinkyVault","role":"other","txHash":"0x4b71a9afc82e925c04588e0bc7eb12525ad47400aceaed479051bf5538e5d65f"},{"address":"0x19bec7c2e1b2aadaf67b259744751a9960d66000","blockNumber":84236520,"name":"PoolInitializationGuard","role":"hook","txHash":"0x4b71a9afc82e925c04588e0bc7eb12525ad47400aceaed479051bf5538e5d65f"},{"address":"0xb125c461c00863aacad4cc99311d2f23d4342a6f","blockNumber":84236520,"name":"LaunchToken","role":"token","txHash":"0x4b71a9afc82e925c04588e0bc7eb12525ad47400aceaed479051bf5538e5d65f"},{"address":"0x577e613e32492b9d8bc364978ddfa376d790b612","blockNumber":84236520,"name":"MerkleDistributor","role":"distributor","txHash":"0x4b71a9afc82e925c04588e0bc7eb12525ad47400aceaed479051bf5538e5d65f"},{"address":"0x17fb2067652bcf4803e937cf323a248ff6b7cba3","blockNumber":84236520,"name":"PinkyStaking","role":"other","txHash":"0x4b71a9afc82e925c04588e0bc7eb12525ad47400aceaed479051bf5538e5d65f"}],"id":"6e9ac2a3-b5e4-4e2d-bf78-b61a8e2af6be","manifestHash":"05beb88f6eb9d1eb7b5d48f007243d0da665b3dfd048c412964eafea68b862d3","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":"13613dc2-172f-4b34-b1a6-17de4f20f4df","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"791595ec660550bdc5cbb5ef4b01d265a38665edbc74c34f617818e5ee1ab347","dependsOn":["audit_imported_code"],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"c4394d5ba6f208bb960515c2bbb521f71bc41923b5191ee7850f156c16ceb372","dependsOn":["adapt_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":"0b45585db22dad8643d30de978f16fb62eca7ad7d4773239fc47b7311cfcf594","dependsOn":["adapt_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":"4e8dda0ae11261c9ea645dba6643a6cf831a3b26d35add0f261440a2657bd395","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"c22c12609e6dbddea52a97a8c3fe273c3701873cc224039765d8fe7253f2517e","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"528bfc56dc477f689c7a2e71c928e07dd2a7f68fd7be81923115fd760fcfde69","dependsOn":["adapt_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":"1103d6e877fa094ab3b2bf6393377b48a7f1b0717cb4184d2d28e63a365ab2be","dependsOn":["adapt_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":"b952fd8456fac332552be186063577731b2c48847547bdae840bebb2a4c2ae5e","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"23c319f03b28628c2e9bb057f89b5f79cbede1891cc2dd8d0074cc84ffcc0797","dependsOn":["adapt_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":"Launch Pinky from this repository on Robinhood Chain, paired with IMD. Token name: Pinky. Token symbol: PINKY. Deploy PinkyStaking, then PinkyVault, with the constructor arguments in launch.json. PinkyVault holds an IMD bond behind a deployer's promise not to transfer more than maxOut of a token out of their wallet before a deadline; after the term it buys a log-sum answer from the IdentityMD oracle through the Intake, paid from the bond, and pays out on the verdict. PinkyStaking streams forfeited IMD to PINKY stakers. Keep the contracts as they are unless the launch requires a change.","parentJobId":null,"planHash":"eccd3d8ff9e1e98e8377ba44d3bf629b6ff5b112a2ae97882cfc3227dbdb9713","previousHash":"5219aabee39c65c99a8c2af858e0e31f1c9437b3492e887c0f8a3848655273f1","projectId":"13613dc2-172f-4b34-b1a6-17de4f20f4df","publication":{"commit":"1476f8303d30a8d2178bee81a41a7b9c3c5c5326","deliveredAt":"2026-10-09T14:43:13.513Z","repoUrl":"https://github.com/identity-md-launches/launch-1166-pinkystaking-pinkyvault"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"074a1e13a15bb0c43d4fc3dd12898fc99830ab7184736c981f71af146f24abaa","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2565f234b0a569e9","findings":[{"citation":"resolved","description":"The vault's guarantee rests on an implicit assumption that the oracle can answer every window the vault can produce. The maker chooses `duration` up to MAX_DURATION (30 days). Robinhood Chain produces about 10 blocks per second (measured over the public RPC on 2026-10-09: blocks 84,097,898 to 84,197,898 span 10,218 s; 83,197,898 to 84,197,898 span 102,529 s), so a 30-day term is a `[fromBlock, toBlock]` window of roughly 25.9 million blocks, and even the 10-minute minimum is ~6,000 blocks. The only windows the oracle is known to have attested for this recipe are 1,000 blocks (request c5e21f11-5172-4f9b-a506-9babcdfa47c2, whose panel members report scanning with `scan.mjs --chunk 10`, i.e. ten blocks per eth_getLogs call, taking 2 min 15 s) and, per README.md, about 18,000 blocks. When the panel cannot complete the scan the request ends in status 2 and no callback is made; `ask` can be repeated twice more at 0.5 IMD each, after which `refund` (line 301) returns the rest of the bond to the maker whatever they did. So the longer the promise, the more likely it is unenforceable, and the party who benefits is exactly the maker who broke it. This is a First-Principles finding about an off-chain capacity assumption: the on-chain path (no callback -> three asks -> refund) is reproduced in the project's own tests; the oracle's inability to scan a multi-million-block window is inferred from the chunk size and durations in the live record, not demonstrated, and should be settled with one live request over a window of at least a few days' blocks before launch. Suggested fix: bound the term in blocks the oracle has demonstrably attested (record `startBlock`, and refuse `close`/`ask` for windows longer than a constant such as the largest window attested live), or lower MAX_DURATION to a term that has been exercised against the live oracle, and document the maximum in the README.","line":93,"path":"src/PinkyVault.sol","reproduction":"State: live oracle on chain 4663. Call `make(token, maxOut, 30 days, 10e18)`; after 30 days call `close(id)`: `endBlock - startBlock` is ~25,920,000 at 10 blocks/s (test/scratch/Review.t.sol test_MaxDurationWindowSize asserts this arithmetic with vm.roll). Call `ask(id)`: the Intake takes 0.5 IMD and sends a log-sum over 25.9M blocks. Expected: a signed answer inside ANSWER_TIMEOUT. Actual (if the panel cannot scan the window, as the 10-block chunking in the live record suggests): no callback; after three asks and 24 h, `refund(id)` pays the maker 8.5 IMD although they transferred more than maxOut during the term.","severity":"medium","snippet":"    uint256 public constant MAX_DURATION = 30 days;","title":"A 30-day term produces a ~26-million-block window the oracle has never attested; an unanswerable window refunds a broken promise"},{"citation":"resolved","description":"ADAPTATION.md records a fix so that 'the maker can ask about their own broken promise and collect the 10% bounty' no longer works, and `payout` (line 273) pays no bounty when `v.asker == v.maker`. The check compares addresses only, and `ask` is open to anyone, so a maker who has broken the promise simply asks from any other address they control before a watcher does and receives 10% of the remaining bond back. The forfeited amount that stakers and the burn receive drops from 100% to 90% of the remainder, and the 'no bounty for the maker' guarantee is only cosmetic. The watcher incentive also inverts: a maker who knows they broke the promise has the strongest reason to ask first through a second wallet. Since the maker loses 90% regardless this is bounded, hence low. A fix that preserves the design: drop the special case and document that the bounty is a race anyone can win (including the maker), or pay the bounty only when the verdict is Broken and the asker asked before `endTime + some delay` so the maker cannot trivially front-run, acknowledging that no on-chain rule can distinguish a maker's second wallet.","line":207,"path":"src/PinkyVault.sol","reproduction":"test/scratch/Review.t.sol test_SybilAskerTakesTheMakersBounty: maker `make(token, 1000e18, 1 hours, 10e18)`; after the term anyone `close(id)`; 32 blocks later `accomplice` (any address other than maker) calls `ask(id)`; the Intake delivers a signed attestation with answer 1000e18+1 (Broken); `payout(id)`. Expected per ADAPTATION.md: the maker's side gets nothing. Actual: accomplice receives 0.95 IMD (10% of the 9.5 IMD left), stakers 4.275 IMD, burn 4.275 IMD.","severity":"low","snippet":"        if (v.asker == address(0) || v.asker == v.maker) v.asker = msg.sender;","title":"The 'maker earns no bounty' rule is bypassed by asking from a second wallet"},{"citation":"resolved","description":"The vault always asks with `evidence: \"chain\"`. src/OracleAttestation.sol lines 48-51 say `agreed` is below `quorum` 'only for chain evidence, when members who ran one recipe split and the deployer's own rerun settled which of their answers was whole', i.e. the oracle still signs an answer it has itself reproduced from the chain. The vault refuses such an answer with InvalidAttestation, so the callback reverts, the Intake records it as not delivered, the 0.5 IMD is spent, and after three attempts (or seven days) `refund` returns the bond to the maker regardless of what the signed answer said. In the one live run of this exact question only four of five members agreed (request c5e21f11-...), so with the launch's 7/5 panel a split to 4 of 7 is a realistic state, and the party it favours is a maker who broke the promise. The check follows the oracle-consumer skill's general advice ('Require agreed >= quorum'), so this is a design trade-off rather than a bug in isolation; report it so the author can decide. Alternatives that keep the owner's settings intact: accept `agreed < quorum` for this recipe because the deployer's rerun is the authority for chain facts, or keep the check and lower the launch quorum so a one- or two-member split still agrees.","line":245,"path":"src/PinkyVault.sol","reproduction":"test/scratch/Review.t.sol test_SplitPanelAnswerRefusedThenRefundedToABrokenMaker: `make(token, 1000e18, 1 hours, 10e18)`, `close`, then three times: `ask(id)` and have the Intake deliver a correctly signed attestation with panelSize 7, quorum 5, agreed 4, answer 1000e18+1 (Broken). Each delivery reverts InvalidAttestation. After the third 24 h timeout `refund(id)` succeeds. Expected: a Broken verdict and forfeiture. Actual: maker ends with 98.5 IMD of their 100 (only the three fees lost).","severity":"low","snippet":"                || a.panelSize < t.panelSize || a.quorum < t.quorum || a.quorum > a.panelSize || a.agreed < a.quorum","title":"A signed chain-evidence answer with agreed < quorum is refused, which refunds a broken promise after three attempts"},{"citation":"resolved","description":"By design (ADAPTATION.md) `lastUpdate` stops while `totalStaked == 0`, so stream time that passes unstaked is paid to the next staker instead of being stranded. The consequence is that the credit is instantaneous and independent of stake size: `earned()` for the new staker is `balance * (rewardPerToken() - paid) / 1e18` where `rewardPerToken()` catches up over the whole idle gap against the new, possibly tiny, `totalStaked`. A bot that watches `totalStaked` fall to zero mid-stream can stake 1 wei of PINKY, `claim()` the entire accrued idle amount and `unstake()` in the same block. Not a loss of funds and the author chose it over stranding; recorded so the trade-off is visible. If undesired, cap the catch-up at `block.timestamp` of the stake (set `lastUpdate = lastTimeRewardApplicable()` whenever `totalStaked` goes from 0 to non-zero, which strands the idle part) or add a minimum stake.","line":53,"path":"src/PinkyStaking.sol","reproduction":"`stake(1e18)` by A; `notify(7e18)` (rate 1e18/day); A `unstake(1e18)` at day 1; nobody staked for 5 days; at day 6 B calls `stake(1)` (1 wei) and in the same block `earned(B)` is ~5e18; B `claim()`s 5 IMD and `unstake(1)`.","severity":"info","snippet":"        if (totalStaked != 0) lastUpdate = lastTimeRewardApplicable();","title":"After a period with nobody staked, the first staker (even 1 wei) is credited the whole idle stream in the same block"}],"hash":"0b45585db22dad8643d30de978f16fb62eca7ad7d4773239fc47b7311cfcf594","nodeId":"9f347637-7e00-42ac-be1e-e623ca757d05","outcome":"completed","summary":"Review complete. The findings file is written at the repository root with four findings and a coverage row for all 16 entry points plus three invariants.\n\n**What I checked**\n\n- Traced every PinkyVault and PinkyStaking entry point through its guards, state writes and failure paths against the Execution Trace, Periphery and First Principles guides.\n- Confirmed OracleAttestation.sol is byte-identical to the protocol copy, the conformance test deploys PinkyVault at the vector address, and the callback uses 89k gas cold under the 200k stipend.\n- Verified against Robinhood Chain and the oracle API: ArbSys block numbers match the RPC, the Intake quotes 0.5 IMD, the signer in launch.json matches the live attester, and the body the vault builds (including toleranceBps) is the one the oracle attested on 2026-10-09.\n- launch.json constructor arguments match both constructors in declaration order; constructors make no external calls.\n\n**Findings**\n\n1. **Medium.** A 30-day term yields a window of about 26 million blocks (measured ~10 blocks/s). The oracle has only been shown to answer 1,000 to 18,000-block windows, with panel members scanning ten blocks per call. An unanswerable window ends in refund, so long promises favour a maker who broke them. The oracle-side failure is inferred, not demonstrated. I recommend one live request over a multi-day window before launch.\n2. **Low.** The \"maker earns no bounty\" rule compares addresses only. A maker's second wallet asks first and takes 10% of the forfeited bond. Reproduced in a scratch test.\n3. **Low.** A signed chain-evidence answer with agreed below quorum is refused, and three refusals refund a broken maker. This follows the skill's guidance, so it is a trade-off for the author to decide. Reproduced.\n4. **Info.** After a gap with no stakers, a 1-wei staker is credited the whole idle stream in one block. This is the documented design.\n\nNo critical or high defects were found, so no proof files are attached. All 46 project tests pass unchanged. The scratch tests under test/scratch/ are mine and need not be kept.","treeHash":null,"usage":{"cachedInputTokens":1384779,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":47017,"runtime":"claude","turns":35,"wallClockMs":733616}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"229c8cb0d9067fbe","findings":[{"citation":"resolved","description":"Seam: access x economics x asymmetry. `payout` withholds the 10% bounty only when `v.asker == v.maker` (src/PinkyVault.sol:273), and `ask` is permissionless with `v.asker` set to whoever asks first (src/PinkyVault.sol:207: `if (v.asker == address(0) || v.asker == v.maker) v.asker = msg.sender;`). The actor the rule tries to exclude controls any number of wallets. A maker who knows they broke the promise calls `close` themselves at `endTime`, so they know `endBlock` exactly, and at `endBlock + SETTLE_DELAY_BLOCKS` calls `ask` from a second wallet; that wallet is recorded as `asker` and `payout` sends it 10% of the remaining bond. The README promises the opposite ('A maker who asked about their own promise gets no bounty; that share burns or goes to stakers with the rest') and ADAPTATION.md lists it as a fixed audit finding. The replacement rule in `ask` (a watcher who re-asks replaces a maker-asker) only executes when no verdict lands within ANSWER_TIMEOUT (1 day); the live oracle answered the vault's own question in 2 minutes 15 seconds (README 'Checked against the live oracle'), so in the normal case the rule never runs. The same mechanism lets a maker who asks first as themselves deny a watcher's bounty at zero cost: the watcher's `ask` in the same block reverts `TooEarly` and the verdict lands with `asker == maker`, bounty 0. Victims: PINKY stakers and the burn, who under the documented split are owed the whole forfeited remainder, receive 90% of it. Impact is bounded (10% of each forfeited bond) and the maker still loses the other 90%, hence low; it is reported because the guarantee is documented and the prior fix is ineffective. Fix: no address comparison can exclude the maker. Either document the bounty as a 10% rebate to whoever asks first (maker included) and delete the maker-asker special-casing in `ask` and `payout`, or change the incentive so the maker cannot profit from asking (for example pay the asker a fixed fee from the bond on both verdicts so the bounty is not a prize the maker wants to win, and send the full forfeit to stakers and the burn).","line":273,"path":"src/PinkyVault.sol","reproduction":"State: PinkyVault with the launch arguments (minBond 5 IMD, Intake quoting 0.5 IMD), one PINKY staker. 1) maker M calls make(token, maxOut = 1000e18, duration = 1 hours, bond = 100e18). 2) M moves more than 1000e18 of token out of their wallet during the term. 3) at endTime M calls close(id). 4) at endBlock + 32, M's second wallet S calls ask(id); promises(id).asker == S. 5) the Intake delivers the signed attestation with answer 1000e18 + 1 -> status Broken. 6) anyone calls payout(id). Actual: S (the maker) receives (100e18 - 0.5e18) / 10 = 9.95 IMD, stakers are notified 44.775 IMD, 44.775 IMD burns. Expected per README/ADAPTATION: the maker's side receives 0, stakers 49.75 IMD, burn 49.75 IMD. Variant (free denial): M calls ask(id) as M at endBlock + 32; a watcher's ask(id) in the same block reverts TooEarly; the verdict lands within minutes with asker == M; payout pays the watcher 0. Reproduced in test/scratch/Review.t.sol: test_MakerSecondWalletCollectsTheBountyOnTheirOwnBrokenPromise and test_MakerAskingFirstDeniesTheWatcherBountyWhenTheOracleAnswersPromptly (both pass on the current code, asserting the outcome above).","severity":"low","snippet":"        uint256 bounty = v.asker == v.maker ? 0 : amount * BOUNTY_BPS / 10_000;","title":"Broken-promise bounty is collected by the maker's second wallet; the 'maker gets no bounty' rule is sybil-bypassable and the watcher bounty is deniable for free"},{"citation":"resolved","description":"Asymmetry (validation present in one side of the request pair, missing in the other). `make` reads `price = intake.priceOf(action, address(imd))`, checks only `bond < price * MAX_ATTEMPTS` (src/PinkyVault.sol:155) and freezes the quote as the promise's ceiling at src/PinkyVault.sol:169 (`terms[id].maxPrice = price;`). `ask` refuses a zero quote and anything above the ceiling at src/PinkyVault.sol:201 (`if (price == 0 || price > v.bond || price > t.maxPrice) revert InvalidPayment();`). So a promise made while the quote is 0 has `maxPrice == 0` and is unaskable forever: with a zero quote `ask` reverts on `price == 0`, and once a real quote returns, or the owner repairs `action`, it reverts on `price > t.maxPrice`. The only exit is `refund` after REFUND_GRACE, which returns the whole bond to the maker regardless of what they transferred; watchers can never earn a bounty on it and the promise shows on chain as a bonded, running promise. The state is reachable without a malicious actor: the live Intake on Robinhood Chain (0x1397434cd35e8a9c8ac312a61d3a285eb31dea56) returns 0 rather than reverting from priceOf(action, IMD) for an action it does not sell (checked on chain with the id for `oracle.request@oracle-2`: 0, while `oracle.request@oracle-1` returns 500000000000000000). It arises when the protocol retires `oracle-1` before the owner switches to `oracle-2` (the oracle-consumer reference says this rotation is expected and must not need a redeploy), or when the owner sets a mistyped action id. Fix: in `make`, revert (for example `InvalidPayment`) when `price == 0`, mirroring `ask`; optionally also let the owner migrate stranded promises by treating `maxPrice == 0` as 'no ceiling' in `ask`.","line":155,"path":"src/PinkyVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PinkyVault} from \"src/PinkyVault.sol\";\nimport {PinkyStaking} from \"src/PinkyStaking.sol\";\nimport {MockERC20, MockIntake} from \"test/mocks/Mocks.sol\";\n\n/// @dev Fails on the current code: `make` accepts a bond while the Intake quotes 0 for the action,\n/// and `ask` can never pay for that promise afterwards (price 0 and maxPrice 0 are both refused),\n/// so the only exit is a refund to the maker whatever they did with the token. Passes once `make`\n/// refuses a zero quote the way `ask` does.\ncontract ZeroQuoteProofTest is Test {\n    bytes32 constant ACTION = bytes32(\"oracle.request@oracle-1\");\n\n    MockERC20 imd;\n    MockERC20 token;\n    MockIntake intake;\n    PinkyVault vault;\n    address maker = makeAddr(\"maker\");\n\n    function setUp() public {\n        vm.warp(1_790_000_000);\n        vm.roll(1_000);\n        imd = new MockERC20(\"Identity.md\", \"IMD\");\n        MockERC20 pinky = new MockERC20(\"Pinky\", \"PINKY\");\n        token = new MockERC20(\"Meme\", \"MEME\");\n        // The live Intake returns 0 from priceOf for an action it does not sell.\n        intake = new MockIntake(makeAddr(\"payee\"), 0);\n        PinkyStaking staking = new PinkyStaking(address(pinky), address(imd));\n        vault = new PinkyVault(\n            makeAddr(\"owner\"), address(imd), address(staking), address(intake), ACTION, vm.addr(0xA11CE), 5 ether, 7, 5, 86_400\n        );\n        imd.mint(maker, 100 ether);\n        vm.prank(maker);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function test_MakeRefusesAPromiseTheVaultCanNeverAskAbout() public {\n        vm.prank(maker);\n        try vault.make(address(token), 0, 1 hours, 100 ether) returns (uint256 id) {\n            // Accepted. Show that no later quote lets anyone ask about it.\n            intake.setPrice(0.5 ether);\n            vm.warp(vm.getBlockTimestamp() + 1 hours);\n            vm.roll(vm.getBlockNumber() + 36_000);\n            vault.close(id);\n            vm.roll(vm.getBlockNumber() + vault.SETTLE_DELAY_BLOCKS());\n            (bool asked,) = address(vault).call(abi.encodeWithSelector(PinkyVault.ask.selector, id));\n            assertTrue(asked, \"make accepted a bond under a zero quote and ask can never pay for it\");\n        } catch {\n            // make refused the zero quote: fixed.\n        }\n    }\n}","reproduction":"State: PinkyVault with the launch arguments; Intake.priceOf(action, IMD) returns 0 (live behaviour for an unsold action id). 1) maker calls make(token, 0, 1 hours, 100e18): succeeds, terms(id).maxPrice == 0, bond pulled. 2) the quote returns to 0.5 IMD. 3) after endTime anyone calls close(id); 32 blocks later any watcher calls ask(id). Actual: reverts InvalidPayment (price 0.5e18 > maxPrice 0). With the quote still 0, ask(id) also reverts InvalidPayment (price == 0). 4) after closedAt + 7 days refund(id) returns 100e18 IMD to the maker although the maker moved any amount of token during the term. Expected: make refuses the promise (the vault cannot buy an answer for it), or ask is later able to pay for it. Proof: test/scratch/ZeroQuoteProof.t.sol fails on the current code and passes once make refuses a zero quote (or once ask can pay for such a promise).","severity":"low","snippet":"        if (bond < minBond || bond < price * MAX_ATTEMPTS) revert BondTooSmall();","title":"`make` accepts a bond while the Intake quotes 0, creating a promise that can never be asked and is refunded to the maker whatever they did"},{"citation":"resolved","description":"Documented in README 'Trust and known limits' and kept here as the Access Control area's record of privileged reach, with actor and preconditions. Actor: the single immutable `owner` ($owner from launch policy). Powers: `setSigner` replaces the key every pending and future attestation is verified against, so the owner can sign any Kept/Broken verdict for any Asked promise (block range and chain id are the only bindings and both are public); `setProtocol` substitutes the Intake that `ask` pays, so an owner-controlled intake can collect the quote recorded at make, at most MAX_ATTEMPTS (3) times per promise (0.5 IMD x 3 at today's quote), and can then call `onOracleResult` as the asked intake. Combined with a sybil asker and a PINKY stake the owner can convert any open bond into 10% bounty + a share of the 45% staker stream, with 45% burned. Mitigations present: the per-promise price ceiling from make, immutable IMD/staking/minBond, and setters that cannot move bonds directly. No path lets a non-owner exercise these powers (setters are onlyOwner, the signer is only writable through them, promiseIdFor is only written by ask with the owner's intake). Nothing to fix in code; the launch should state who holds $owner and whether it is a multisig.","line":313,"path":"src/PinkyVault.sol","reproduction":"Precondition: owner key. 1) owner calls setSigner(ownerKey). 2) for any promise in status Asked the owner signs an Attestation with chainId = 4663, fromBlock/toBlock = the promise's startBlock/endBlock, panelSize 7, quorum 5, agreed 5, answerType 3, answer = abi.encode(maxOut + 1) in the vault's EIP-712 domain. 3) the asked Intake (or an owner intake set earlier with setProtocol before the ask) delivers it: status Broken although the maker kept the promise. 4) payout: 10% to the asker, 45% to stakers, 45% burned. Expected under honest ownership: only the IdentityMD attester's answer decides. This is the README's stated trust assumption; reported as info.","severity":"info","snippet":"    function setSigner(address signer_) external onlyOwner nonReentrant {","title":"Trust assumption (not a defect): the vault owner can forge verdicts and divert up to three oracle fees per promise; no unprivileged amplifier found"}],"hash":"1103d6e877fa094ab3b2bf6393377b48a7f1b0717cb4184d2d28e63a365ab2be","nodeId":"152f9062-70ab-4635-b0fd-1512598e1a39","outcome":"completed","summary":"The review is complete. No tracked file was changed; the only writes are `.imd-findings.json` in the repository root and two scratch tests under `test/scratch/`.\n\n**Outcome: the permission model holds. Two low defects and one documented trust assumption, all in `.imd-findings.json`.**\n\nWhat I checked in the assigned area (Access Control, Trust Gap, Asymmetry):\n\n- **Every privileged path** is `onlyOwner` with non-zero checks, the signer is writable only through the owner setter, and the oracle callback is bound to the intake that was asked via a mapping only `ask` writes. An outsider's own Intake request cannot be matched to a promise.\n- **Paired surfaces** (`make`/`payout`/`refund`, `ask`/`onOracleResult`, `stake`/`unstake`, Kept/Broken branches) mirror their state writes. Bonds are zeroed before transfers and the staking approval is always reset.\n- **Live inputs verified on Robinhood Chain**: IMD and the Intake have code, the quote is 0.5 IMD for the manifest action id, ArbSys answers, the signer is an EOA, and the cited oracle request is attested with the vault's exact body shape. The vendored OpenZeppelin is identical to upstream v5.5.0 apart from line endings. All 46 project tests pass.\n\nFindings:\n\n1. **Low. Maker captures the watcher bounty from a second wallet.** The `asker == maker` exclusion in `payout` is defeated by asking from any other address at the first eligible block. Stakers and the burn receive 90% of a forfeited remainder instead of all of it, and a maker can deny a watcher's bounty for free. The README states the opposite guarantee and the earlier audit recorded it as fixed.\n2. **Low. `make` accepts a bond under a zero Intake quote.** The live Intake returns 0 for an action it does not sell, so after an action retirement or a mistyped action id, every new promise freezes a zero price ceiling and can never be asked. The only exit is a refund to the maker whatever they transferred. This finding carries a proof test that fails on current code.\n3. **Info. Owner trust assumption.** The owner can forge verdicts via `setSigner` and divert up to three oracle fees per promise via `setProtocol`. This is documented in the README and has no unprivileged amplifier, so it is recorded with actor and preconditions rather than as a defect.\n\nCoverage: all 16 entry points have a row. Thirteen hold, three point at findings 1 and 2. One row is marked unreached for oracle behaviour on 30-day windows and split panels, which is outside my area and not reproducible offline.","treeHash":null,"usage":{"cachedInputTokens":2182531,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":72495,"runtime":"claude","turns":44,"wallClockMs":1304065}},{"artifacts":[],"attempt":1,"bundleHash":"8c1b84352a367bdfdc715eed36b05636d415c49bbfd3e643b6d2e5776a849be4","device":"357c46e3781993d4","findings":[],"hash":"23c319f03b28628c2e9bb057f89b5f79cbede1891cc2dd8d0074cc84ffcc0797","nodeId":"81718a94-8ecd-4ff7-8cd9-af0eba490984","outcome":"completed","summary":"All checks pass and only files under `test/` changed. The scratch test is outside the deliverable and is discarded automatically. Nothing is left to request.\n\n**Outcome:** the suite builds and passes after the other contributor's contract changes, with 137 tests green (up from 128 on the broken tree). No defect that cannot be tested around was found, so no findings file was written.\n\n**What was broken and why.** My previous tests were accepted against an older `PinkyVault` and `PinkyStaking`. The merge then applied a review round that grew the `terms` getter to five fields, made `make` refuse a zero oracle quote, let the original Intake raise its price against the bond, paused the staking stream while nobody is staked, and stopped `notify` from lowering the rate. Three destructurings no longer compiled and two staking tests asserted the old rules.\n\n**What changed, all under `test/`:**\n- `PinkyVaultEdges.t.sol`: fixed the three getter reads, replaced the zero-quote test with one where the quote drops to zero after `make`, and added three tests the other contributor did not cover. Switching the owner's protocol away and back keeps the cap at the other Intake and the full bond room at the original. A price rise to the whole bond leaves a kept maker nothing, pinned as the review's documented trade-off. A second ask after a rise is held to what is left, down to a zero refund.\n- `PinkyStakingEdges.t.sol`: rewrote the two tests for the pause-and-resume stream and the rate floor, and added the exact boundary of the floor, one wei under it, a floor-sized `notify` at the tail of a fast stream, the no-resume branch when the stream ended with stakers, a partial unstake that must not pause, and two fuzz properties: `notify` never slows or shortens a running stream, and a pause of any length credits nothing to whoever resumes.\n- `PinkyStakingInvariant.t.sol`: the handler now asserts on every resume and every `notify`, and a new invariant checks that a paused stream is frozen.\n- `PinkyInvariant.t.sol`: the vault handler now moves each Intake's price including to zero, switches between two Intakes, and probes the three refusals inside `ask` with `expectRevert` instead of skipping them. Bond accounting tracks fees at the price each ask actually paid.\n\n**Worth knowing.** The one behaviour I pinned rather than reported is that anyone can spend a promise's entire remaining bond on an oracle request once the original Intake quotes that much. The implementer and the reviewer chose that deliberately over leaving promises unaskable, and the ADAPTATION notes record it, so a test marks the rule rather than a finding reopening it.","treeHash":"fb6dce923ffe31ee3d047f1a1bbaeb6588d63809","usage":{"cachedInputTokens":1715235,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":44539,"runtime":"claude","turns":48,"wallClockMs":823197}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5fd2de06561c50a4","findings":[],"hash":"2b4323d100c7f62c30c7a589926695046da23206c3627f923ea0bd82a9e5cf2f","nodeId":"6bb66b88-b3af-44ac-b3da-568aa1a6d57d","outcome":"failed","summary":"the task produced no changes; the agent's last message was: Taylor, the manifest already matches the accepted tree. No change to launch.json was needed this round.\n\n**What I checked**\n\n- The latest accepted commit changed PinkyStaking's stream math and PinkyVault's per-promise Intake terms, but neither constructor signature changed. The manifest's argument lists still match, one word per parameter in declaration order.\n- PinkyStaking takes the launch token and IMD. PinkyVault takes the policy owner, IMD, PinkyStaking, the Robinhood Chain Intake, the padded action id, the oracle signer, a 5 IMD minimum bond, panel 7, quorum 5, and one day of answer validity. Those are the values the README commits to, and they sit inside the vault's own setter bounds.\n- Pool fields are exactly the launch's: IMD as the pair, fee 12500, tick spacing 60, the one to one sqrt price.\n- A scratch test in test/scratch deploys all three contracts on an empty chain with the manifest's literal words and reads every setting back. It passes, so the constructors call no other contract.\n- A schema check on launch.json passed: only the five allowed keys, lowercase addresses, backward-only contract references, address references only in address slots, notes under the limit.\n\n**Results**\n\n```\nforge build: ok\nforge test:  54 passed, 0 failed (6 suites, including the scratch manifest test)\n```\n\nOnly test/scratch was touched, and that directory is discarded before submission. launch.json and every other file are unchanged from the accepted state.","treeHash":null,"usage":{"cachedInputTokens":432330,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":4867,"runtime":"claude","turns":9,"wallClockMs":91953}},{"artifacts":[],"attempt":1,"bundleHash":"db5aeb61ca0154a9ca71bd89a0d32645da1d2580362a9aacb7513d26d4486ff1","device":"f768e94767a9dde3","findings":[],"hash":"3ae08bf7d917dab195d713506f68627ce050b6425055a68fd4b0d07e135160c2","nodeId":"3f4e0118-3f81-4e10-8d06-f1a9432af393","outcome":"completed","summary":"Done. The project builds and all tests pass with its own configuration, and the work is confined to source, tests, README, ADAPTATION.md and one manifest value.\n\n**What I found**\n\nThe repository already met the factory's shape: a fixed-supply LaunchToken named Pinky / PINKY, nonpayable constructors with the owner as an argument, no proxies or forbidden opcodes, and constructors that call no other contract. The attestation library is byte-identical to the protocol reference. All six actionable audit findings reproduced against the unchanged code in scratch tests.\n\n**What I changed**\n\n- `src/PinkyStaking.sol`: stream time that passes with nobody staked is now credited to the next staker instead of being stranded forever. One line in the reward modifier.\n- `src/PinkyVault.sol`: a per-promise `terms` record. It stores the oracle price seen at make time, so the owner cannot redirect a bond through a dearer intake, and the panel each request was bought with, so a panel change mid-flight no longer refuses a correct answer. The callback now requires that at least quorum members agreed. A maker who asks about their own broken promise earns no bounty, and the first watcher keeps the bounty across re-asks. A re-ask no longer kills the earlier request, so a late valid answer still counts.\n- `launch.json`: pool fee 3000 changed to 12500, the only value the launch terms required. Constructor arguments are untouched because the constructor signatures did not change.\n- Tests: new `test/LaunchToken.t.sol` and `test/PinkyStaking.t.sol`, nine new vault tests including a cold-storage 200,000 gas check, and one existing test rewritten because it encoded the old re-ask behaviour.\n- `README.md` and `ADAPTATION.md`: every change, the finding or rule that required it, the two info findings left as documented trust assumptions, and the launch section.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| forge build | passes |\n| forge test | 46 project tests pass |\n| scratch floor emulation on chain 4663 | passes, vault runtime 12,528 bytes, no forbidden opcodes |\n\nNothing was run against the live chain, and no commit was made since the git directory is out of scope for this task.","treeHash":"df46c6f97e697cfd25618465b044ca7f90cc1b87","usage":{"cachedInputTokens":3893900,"inputTokens":1218,"model":"claude-fable-5-1","outputTokens":45437,"runtime":"claude","turns":39,"wallClockMs":678234}},{"artifacts":[],"attempt":1,"bundleHash":"facb04db7824c772ccbd021aaa0ca04d205e9e3889cd439876abef54b82059fa","device":"8c9c4986ca881107","findings":[{"description":"The audit fix for stranded rewards freezes `lastUpdate` while `totalStaked == 0`, so the stream time that passes with nobody staked is credited to the next staker. That credit lands at once: `rewardPerToken()` includes the whole gap on the first call after the stake, so a staker can stake 1 wei of PINKY, `claim()` the entire idle accrual and `unstake()` in the same transaction. Nothing is lost to the contract, but the idle IMD goes to whoever watches the mempool rather than to stakers over time. An alternative that keeps the fix is to pause the stream (extend `periodFinish`) while nobody is staked. This is a design trade-off, documented in the README, and is reported so the reviewer can decide, not as a defect in the accounting: the invariant suite shows every wei is still accounted for.","line":53,"path":"src/PinkyStaking.sol","reproduction":"Stake 1 ether, notify 7 ether, unstake everything, warp 2 days, then from a fresh address: stake 1 wei, claim, unstake. Expected (if the stream paused while idle): the fresh address earns ~0. Actual: it receives ~2 ether of IMD at once. Test: test/PinkyStakingEdges.t.sol test_TheIdleStreamGoesToTheNextStakerAtOnce.","severity":"low","title":"The idle reward stream is credited in full to whoever stakes next, including a one-wei flash stake in one transaction"},{"description":"`make` records `terms[id].maxPrice = intake.priceOf(action, imd)`. If that quote is 0 at the time (a misconfigured or temporarily free Intake, or an owner-set Intake that does not sell the action), `ask` always reverts with `InvalidPayment`: a zero price is refused outright, and any later non-zero price exceeds `maxPrice`. The bond is then locked until `close` plus the seven-day grace and goes back to the maker whatever they did with their wallet. The live Intake quotes 0.5 IMD so this does not affect the launch as configured, but `make` could refuse a zero quote the way `ask` does, so a maker cannot buy an unsettleable promise.","line":169,"path":"src/PinkyVault.sol","reproduction":"With the Intake price set to 0: make(token, maxOut, 1 hours, 10 IMD) succeeds; close; ask reverts InvalidPayment; set the price back to 0.5 IMD; ask still reverts InvalidPayment; after REFUND_GRACE, refund returns the full 10 IMD. Expected: make refuses a zero quote (BondTooSmall or InvalidPayment). Actual: the promise is created and is only ever refundable. Test: test/PinkyVaultEdges.t.sol test_MakeUnderAZeroQuoteCanNeverBeAskedOnlyRefunded.","severity":"low","title":"A promise made while the Intake quotes a zero price can never be settled by the oracle, only refunded"},{"description":"`notify` is permissionless (the vault is only one caller) and each call re-spreads the remaining rewards over a fresh seven days. A griefer paying `MIN_REWARD` every few days can keep delaying what stakers receive; the rewards are never lost, only slowed, and the griefer's IMD joins the stream. This is the standard Synthetix trade-off and the floor the contract sets is the mitigation. Reported for awareness.","line":99,"path":"src/PinkyStaking.sol","reproduction":"Stake, notify 7 IMD, warp 3 days, notify 0.01 IMD: rewardRate becomes (0.01 + 4) / 7 days and periodFinish moves out by 7 days, so the remaining 4 IMD now takes 7 more days instead of 4. Test: test/PinkyStakingEdges.t.sol test_ANotifyMidStreamRestretchesWhatIsLeft.","severity":"info","title":"Anyone can restretch the stakers' stream for 0.01 IMD per call"},{"description":"The burn share is a plain `safeTransfer` to the dEaD address. If the Robinhood Chain IMD token at 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127 ever refuses that recipient (a blocklist or a burn-address guard), `payout` for every broken promise reverts and the bond is stuck in `Broken` with no other exit (`refund` refuses verdicts). This could not be checked here: the task and the verifier run with no network, and the mock IMD is a plain OpenZeppelin ERC-20. A fork run of test/PinkyVault.t.sol's broken-promise payout against the live IMD is owed before launch.","line":288,"path":"src/PinkyVault.sol","reproduction":"Not reproducible offline. On a fork of chain 4663: deploy the vault against the live IMD, make/close/ask, deliver a Broken verdict with a test signer, call payout; expected: the transfer to dEaD succeeds.","severity":"info","title":"Payout of a broken promise depends on the live IMD token accepting transfers to 0x000000000000000000000000000000000000dEaD"},{"description":"`refund` deletes only `promiseIdFor[v.intake][v.requestId]`, the latest request, and `onOracleResult` deletes only the id it was answered under. The ids of earlier asks remain mapped to the promise forever. They are harmless because every path checks `status == Asked` first (an answer to a stale id reverts WrongStatus, verified in tests), but the mapping is not the clean 'contract forgets it' the oracle-consumer reference asks for, and a reader of `promiseIdFor` sees pending requests that are not.","line":297,"path":"src/PinkyVault.sol","reproduction":"make, close, ask (first), warp 1 day, ask (second), warp 7 days, refund. promiseIdFor[intake][second] == 0 but promiseIdFor[intake][first] == id. Test: test/PinkyVaultEdges.t.sol test_AnAnswerToARefundedRequestIsRefused.","severity":"info","title":"Earlier request ids of a re-asked promise stay mapped after refund or verdict"}],"hash":"42e6d91cd0c09a7a183a95898b26ce56bb27833dd2d698eefeb2560a64456c2d","nodeId":"81718a94-8ecd-4ff7-8cd9-af0eba490984","outcome":"completed","summary":"The findings file is intentionally excluded by the harness's own `.git/info/exclude`, so the platform collects it from the working tree rather than from a commit. Nothing more to do there.\n\n## Recap\n\n**Result:** `forge test` passes with 120 tests (46 existing, 74 new) across 9 suites, two of them invariant suites at 256 runs × 64 depth with zero reverts. No critical or high defect was found in the contracts, so no proof files were needed. Five design-level observations are recorded in `.imd-findings.json` (two low, three info), each tied to a test that demonstrates the behaviour.\n\n**What I added under `test/`:**\n\n- `PinkyVaultEdges.t.sol` (52 tests): constructor zero/boundary refusals; `make` at exact duration and bond boundaries, without allowance, with a fee-on-transfer IMD, under a zero price quote; `close`/`ask` at exact block and time boundaries, with the Arbitrum block path; Intakes that revert, pull the wrong amount, pull nothing, or reuse a request id; the callback with expired, not-yet-valid, wrong-length, inconsistent-panel, replayed, tampered, other-vault and other-chain attestations, from senders that never asked, and after a refund; delivery under the real 200,000 gas stipend from cold storage, including a refused delivery leaving the request pending; payout when the staker share is below the stream floor or the bond is fully spent; refund timing and independence from the Intake; owner setter bounds; three fuzz properties (verdict boundary, exact broken split, exact refund).\n- `PinkyStakingEdges.t.sol` (17 tests): zero amounts, over-unstake, allowances, floor/restretch arithmetic, late and flash stakers, the idle-stream rule, full-supply stake, rounding at one wei, and two fuzz properties (pro-rata at every moment, no over-payment across notifies).\n- `PinkyStakingInvariant.t.sol`: a three-staker, two-notifier handler with invariants that PINKY held equals the stakes, nobody withdraws more than staked, IMD held covers what is owed, nothing is paid twice, and every wei is claimed, owed, streaming, idle or bounded dust.\n- `PinkyInvariant.t.sol` (extended, not rewritten): a transition checker after every handler call so a promise only moves forward and never reopens, a maker-asker action, exact split assertions in payout, and invariants that the bond equals deposit minus fees paid and that verdicts are final.\n- `OracleConsumerVectorKey.t.sol`: the vault at the protocol's vector address, driven through make/close/ask, with the vector's own signature passing the signature check in the real callback and an answer signed by the vector key settling the promise under the stipend.\n- `mocks/MoreMocks.sol` and `utils/PinkyFixture.sol`: a stipend-delivering Intake, misbehaving Intakes, a fee token, and a shared fixture at the launch's configuration.\n\n**Findings reported (none change the launch as configured):**\n- Low: the idle reward stream is credited in full to the next staker, even a one-wei flash stake in one transaction.\n- Low: a promise made while the Intake quotes zero can never be asked, only refunded.\n- Info: permissionless `notify` can restretch the stream for 0.01 IMD per call; stale request ids remain mapped after refund (harmless, status-guarded); and the burn transfer to the dEaD address against the live IMD token is a fork check still owed, since this run had no network.","treeHash":"a343b1ad1d98c8d6ee37f05301cb42296796cd74","usage":{"cachedInputTokens":3793278,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":82789,"runtime":"claude","turns":51,"wallClockMs":1062659}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1731fbfe0c4574fb","findings":[{"citation":"resolved","description":"Synthetix-style accounting: while totalStaked == 0, rewardPerToken() stays flat but updateReward() still advances lastUpdate to lastTimeRewardApplicable(). Every second of the 7-day stream that passes with nobody staked is skipped, and the IMD for those seconds stays in the contract forever. The contract has no owner, no sweep and no re-notify of its own balance, so forfeited IMD that PinkyVault.payout() sent 'to PINKY stakers' becomes unreachable. The vault only checks totalStaked() != 0 at the moment of notify(), which does not protect the following 7 days. With a small early staker set (one staker who unstakes) the whole staker share of a broken bond is lost. Also, rewardRate = amount / DURATION truncates up to 604,799 wei per notify, which is dust but likewise unrecoverable.","line":64,"path":"src/PinkyStaking.sol","reproduction":"State: staker has staked 1e18 PINKY; vault (or anyone) calls notify(1e18 IMD). Same block: staker calls unstake(1e18) (earned == 0). Warp 7 days. Staker stakes 1e18 again, warp 7 more days. Expected: the 1 IMD stream is eventually claimable by stakers. Actual: staking.earned(staker) == 0 and imd.balanceOf(staking) == 1e18 forever; no function can move it. Verified in test/scratch (test_StakingStrandsRewardsWhenEveryoneUnstakes).","severity":"medium","snippet":"        if (totalStaked == 0) return rewardPerTokenStored;","title":"PinkyStaking strands the remaining IMD stream whenever totalStaked drops to zero, with no way to recover it"},{"citation":"resolved","description":"ask() builds the oracle body from the storage panelSize/quorum at request time, but onOracleResult() compares the attestation against the storage values at delivery time. If the owner raises the panel (a routine tuning, e.g. 7/5 -> 9/6) while any request is in flight, the oracle's answer for the old 7/5 request is refused with InvalidAttestation, the callback reverts inside the Intake's try, the writer does not retry, and the 0.5 IMD already paid from that bond is spent for nothing. The promise then needs a second ask (another 0.5 IMD from the maker's bond) or, after three failures, a refund. The requested panel size and quorum should be stored in the Promise at ask() time and compared against those.","line":222,"path":"src/PinkyVault.sol","reproduction":"make(); close(); ask() with panel 7/5 (body carries panelSize 7, quorum 5). Owner calls setPanel(9, 6, 86400). Intake delivers the signed attestation with panelSize 7, quorum 5, agreed 7, correct block range. Expected: verdict recorded. Actual: revert InvalidAttestation; promise stays Asked with bond reduced by 0.5 IMD. Verified in test/scratch (test_SetPanelMidFlightRefusesPendingAnswer).","severity":"low","snippet":"                || a.panelSize < panelSize || a.quorum < quorum || a.quorum > a.panelSize","title":"Panel settings are checked against the current owner values, not the values the request was bought with, so setPanel() during a pending request discards a correct answer"},{"citation":"resolved","description":"The oracle-consumer reference states that panelSize, quorum and agreed are all signed and that a consumer should 'Require agreed >= quorum and that the panel and quorum are at least what the contract asked for'. The vault enforces the second half only. An attestation whose panel did not reach quorum (agreed < quorum, which the protocol can issue for chain evidence when the deployer's rerun settles a split panel) decides Kept/Broken and moves the whole bond. If the project intends to rely on the rerun rather than panel agreement, that should be a deliberate, documented choice; otherwise add `|| a.agreed < a.quorum` to the check.","line":222,"path":"src/PinkyVault.sol","reproduction":"make(); close(); ask(). Deliver a signed attestation with panelSize 7, quorum 5, agreed 0, correct block range, answer 0. Expected (per reference): InvalidAttestation. Actual: accepted, status becomes Kept. Verified in test/scratch (test_AgreedBelowQuorumAccepted).","severity":"low","snippet":"                || a.panelSize < panelSize || a.quorum < quorum || a.quorum > a.panelSize","title":"onOracleResult() does not require agreed >= quorum; an attestation with agreed == 0 is accepted as a verdict"},{"citation":"resolved","description":"ask() is open to anyone, including v.maker, and payout() sends BOUNTY_BPS (10%) of what is left to v.asker when the verdict is Broken. A maker who knows they broke the promise simply calls close() at endTime and ask() 32 blocks later themselves (they are best placed to do so), and gets 10% of the forfeited bond back. The bounty is meant to pay a third-party watcher; it should not be claimable by the maker (or the asker share should be burned when asker == maker). Note also that a re-ask after ANSWER_TIMEOUT overwrites v.asker, so the watcher who paid gas for the first ask loses the bounty to whoever re-asks.","line":193,"path":"src/PinkyVault.sol","reproduction":"maker: make(token, maxOut 1000e18, 1h, bond 10e18); after endTime: close(); +32 blocks: maker calls ask(). Oracle answers out = 1001e18 -> Broken. payout(): maker receives (10e18 - 0.5e18) / 10 = 0.95e18 IMD. Expected: a broken maker receives nothing (README: 'Break it and it doesn't [come home]'). Verified in test/scratch (test_MakerCollectsBountyOnOwnBrokenPromise).","severity":"low","snippet":"        v.asker = msg.sender;","title":"The maker can call ask() on their own broken promise and collect the 10% asker bounty, reducing the penalty to 90% of the remaining bond"},{"citation":"resolved","description":"This is an intentional owner power (the oracle reference requires the intake to be owner-settable), reported as a trust assumption for the README and the adapter, not as a bypass. ask() takes the price from whatever intake the owner has set and only refuses a price larger than the remaining bond, so an intake contract whose priceOf() returns exactly v.bond takes the entire bond of every open promise the next time anyone asks. The README documents that the owner can forge verdicts via setSigner but not that bonds can be redirected via setProtocol. A cheap mitigation that keeps the design: record the price observed at make() in the Promise and refuse an ask whose price exceeds it (or cap price at minBond / MAX_ATTEMPTS).","line":187,"path":"src/PinkyVault.sol","reproduction":"make() with bond 10e18 against the real intake (price 0.5e18); close(). Owner: setProtocol(G, action) where G.priceOf() returns 10e18 and G.request() pulls `amount`. Anyone: ask(id). Actual: imd.balanceOf(G) == 10e18, vault balance 0, promise bond 0, status Asked. Verified in test/scratch (test_OwnerIntakeSwapDrainsBond).","severity":"low","snippet":"        if (price == 0 || price > v.bond) revert InvalidPayment();","title":"Owner trust assumption not in the README: setProtocol() can point ask() at an intake whose price equals a bond, moving the whole bond to that intake"},{"citation":"resolved","description":"When a request is more than one day old, any address may call ask() again. That deletes the mapping for the first request id, so an answer the writer delivers afterwards for the first request reverts with UnknownRequest (and is not retried), while a second oracle fee is taken from the bond and v.asker is replaced by the re-asker. A third party can do this up to MAX_ATTEMPTS times, costing the maker up to 1.5 IMD and the original watcher their bounty. Bounded by MAX_ATTEMPTS and the 1-day timeout, so low; consider letting only the maker or the original asker re-ask, or keeping earlier request ids answerable until a verdict lands.","line":178,"path":"src/PinkyVault.sol","reproduction":"make(); close(); watcher ask() -> r1. Warp +1 day. griefer ask() -> r2. Intake delivers a valid signed attestation for r1. Expected: a late valid answer counts. Actual: revert UnknownRequest; bond is 10e18 - 2 * 0.5e18; asker is now the griefer. Verified in test/scratch (test_ThirdPartyReaskDiscardsLateAnswer).","severity":"low","snippet":"            delete promiseIdFor[v.intake][v.requestId];","title":"Anyone can re-ask after ANSWER_TIMEOUT, which kills a still-valid pending answer and spends another 0.5 IMD of the maker's bond"},{"citation":"resolved","description":"The promise is 'not before the deadline' but the window the oracle sums ends at the block of close(), which anyone may call at any time after endTime. A watcher has an incentive to not close and wait for the maker to transfer, then close, ask and take the 10% bounty. The README documents 'Until then the promise keeps running'; this is recorded so the adapter and the site make it prominent (or so the contract lets the maker pre-register a close block). Self-protecting: the maker can close at endTime.","line":168,"path":"src/PinkyVault.sol","reproduction":"make(token, maxOut 0, duration 1h). At endTime + 1s the maker transfers 1 token (deadline passed, no close yet). Watcher calls close() at endTime + 2s, then ask(); oracle sums the range [startBlock, closeBlock] = 1 > maxOut -> Broken. Expected by a reader of the promise: Kept.","severity":"info","snippet":"        v.endBlock = uint64(_chainBlock());","title":"Transfers after endTime but before someone calls close() are counted, so a maker who waits to be closed by a third party can be judged Broken for moving tokens after the deadline"},{"citation":"resolved","description":"Two promises made and closed in the same blocks (same startBlock/endBlock, different maker or token) are indistinguishable to onOracleResult(); only the writer's choice of which intake request id to deliver each attestation under keeps answers with their promises. The writer is trusted in the protocol, so this is not an exploit path, but the reference recommends pinning the question where the window is absolute (it is here). Recording the request body hash or the oracle's question hash at ask() time would make the vault self-checking.","line":221,"path":"src/PinkyVault.sol","reproduction":"Deliver, via the intake, an attestation with questionHash = keccak256('anything') and a matching block range: accepted. Deliver promise A's attestation under promise B's intake request id when A and B share [startBlock, endBlock]: accepted for B.","severity":"info","snippet":"            a.chainId != block.chainid || a.fromBlock != v.startBlock || a.toBlock != v.endBlock","title":"The vault does not pin questionHash; an answer is bound to a promise only by chain id and block range, relying on the Intake writer's pairing"},{"citation":"resolved","description":"Read in full: src/PinkyVault.sol, src/PinkyStaking.sol, src/OracleAttestation.sol (byte-identical to the oracle-consumer REFERENCE.md copy), src/LaunchToken.sol, src/interfaces/IIntake.sol, test/*.sol, test/mocks/Mocks.sol, launch.json, README.md, requests/*. Verified locally: all 26 project tests pass; the protocol conformance vector (digest 0x95fe..., selector 0x510379c7) passes; callback completes under a 200,000 gas call with cold storage; runtime of PinkyVault (12,093 bytes) and PinkyStaking (2,501 bytes) contain no DELEGATECALL/CALLCODE/SELFDESTRUCT when scanned as Project.protected.t.sol does; neither constructor calls another contract; LaunchToken mints exactly 1e27 'Pinky'/'PINKY' to msg.sender with no mint/pause/blacklist/upgrade paths; launch.json matches the constructor order and types (10 args), uses lowercase addresses that match the reference's Robinhood Chain Intake, IMD and signer, the right action id, names under 32 chars, token without constructorArgs, and PinkyStaking before PinkyVault. Owner setters (setProtocol, setSigner, setPanel) exist by requirement of the oracle-consumer reference and are reported as trust assumptions, not as post-deployment configuration defects. Could not be reached / not verified: the live Intake, IMD and ArbSys behaviour on chain 4663; whether the oracle accepts `toleranceBps` and a fromBlock/toBlock window as wide as MAX_DURATION (30 days, roughly 10M Arbitrum blocks; the README says only ~18,000-block windows were tested, and since 'no answer favours the maker' a long term whose window the oracle cannot process ends in a refund minus fees); the launch's 'This launch' pool section, so pool.fee 3000 / tickSpacing 60 / initialPrice were not checked against it; the policy owner that $owner resolves to.","line":47,"path":"README.md","reproduction":"n/a - coverage statement. The one unverifiable risk with a concrete shape: make(token, 0, 30 days, 5e18); close(); ask() three times over three days with no oracle answer; refund() returns 3.5 IMD to a maker who may have moved everything, so a maker choosing the longest term is judged only if the oracle can sum a ~10M-block window.","severity":"info","snippet":"| `src/PinkyVault.sol` | Bonds, the oracle request, the verdict and the payout |","title":"Coverage: what was read, what was verified, and what could not be reached offline"}],"hash":"4e8dda0ae11261c9ea645dba6643a6cf831a3b26d35add0f261440a2657bd395","nodeId":"40af0743-a9fd-4218-9926-88f6cad78c0a","outcome":"completed","summary":"The review is complete and the findings file is written and validated.\n\n**Outcome.** No critical or high defects were found in the contracts as they arrived, so no proof tests were attached. The findings file at `.imd-findings.json` holds nine entries: one medium, five low, three info. Every snippet was checked against the tree line by line, and each defect was reproduced with a scratch Foundry test under `test/scratch/` before being reported.\n\n**What was read.** PinkyVault, PinkyStaking, OracleAttestation, LaunchToken, the IIntake interface, all project tests and mocks, launch.json, README and the request bodies. OracleAttestation is byte-identical to the reference copy. The protocol conformance vector passes, the callback completes under a 200,000 gas call, and the runtime bytecode of both application contracts contains no forbidden opcodes and is well under the size limit. The token mints exactly one billion PINKY to its deployer with no admin paths. The manifest matches the constructor order and types and the reference's Robinhood Chain addresses.\n\n**Findings, most severe first:**\n- **Medium.** PinkyStaking permanently strands the rest of a reward stream whenever everyone unstakes, and it has no owner or sweep. Forfeited IMD sent \"to stakers\" can become unreachable.\n- **Low.** Panel settings are checked at delivery time against current owner values, so a routine `setPanel` while a request is pending discards a correct answer and wastes the fee.\n- **Low.** The callback does not require `agreed >= quorum`, which the oracle reference asks for. An attestation with agreed 0 decides the bond.\n- **Low.** The maker can call `ask` on their own broken promise and take the 10% bounty, cutting the penalty to 90%.\n- **Low.** Owner trust assumption missing from the README: `setProtocol` can point at an intake whose price equals a bond, moving the whole bond there on the next ask.\n- **Low.** Anyone can re-ask after the one-day timeout, which kills a still-valid late answer and spends another fee from the maker's bond.\n- **Info.** Transfers between `endTime` and whoever calls `close` are counted, a trap for makers who do not close promptly. The questionHash is not pinned, so pairing rests on the writer. A coverage entry lists what was verified and what was not.\n\n**Not reachable offline.** The live Intake, IMD and ArbSys behaviour on chain 4663, whether the oracle accepts `toleranceBps` and a 30-day block window as wide as MAX_DURATION allows, the launch's pool section for fee and tick spacing, and the policy owner that `$owner` resolves to. The widest-window case matters because an unanswerable question refunds the maker.","treeHash":null,"usage":{"cachedInputTokens":1475327,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":38315,"runtime":"claude","turns":27,"wallClockMs":574878}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1a7ecd03bd365366","findings":[{"citation":"resolved","description":"Boundary x invariant seam. make() reads intake.priceOf(action, imd) and only uses it as a floor (bond >= 3*price) and as the per-promise price cap terms[id].maxPrice. At price == 0 the floor check is `bond < 0`, which never fires, and maxPrice is recorded as 0. ask() (src/PinkyVault.sol:201) then reverts InvalidPayment on `price == 0` while the action is unsold and on `price > t.maxPrice` (= `price > 0`) as soon as any price is quoted. The two guards are asymmetric: the entry accepts exactly the value the settlement refuses. The result is a promise whose verdict can never be bought by anyone, under any later owner setting; its only exit is refund() seven days after close, which returns the whole bond to the maker whatever they transferred. The vault's guarantee (a broken promise forfeits the bond) is void for every promise made in that state, while the promise still shows as bonded on chain.\n\nThe zero price is a live value, not a mock artefact: on Robinhood Chain (2026-10-09, `cast call 0x1397434cd35e8a9c8ac312a61d3a285eb31dea56 'priceOf(bytes32,address)(uint256)'`) the Intake returns 500000000000000000 for bytes32(\"oracle.request@oracle-1\") with IMD, 0 (no revert) for bytes32(\"oracle.request@oracle-2\") with IMD, and 0 for oracle-1 with the native asset. The oracle-consumer reference requires that the action id be owner-settable precisely so a new action version (oracle-2) can be adopted without a redeploy, so the window is reached by ordinary operation: the owner stages oracle-2 before the Intake sells it, or the Intake retires oracle-1 (price 0) before the owner switches. Every make() during that window is unprivileged and benefits the maker. The same seam in milder form is already documented in the README: any price rise strands older promises in refund.\n\nMinimal fix: in make(), `if (price == 0) revert InvalidPayment();` (or BondTooSmall) next to the existing floor, so a promise is only recorded under terms an ask() can satisfy. Optionally also let the owner raise maxPrice for stranded promises, but that is a design choice.","line":154,"path":"src/PinkyVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PinkyVault} from \"src/PinkyVault.sol\";\nimport {PinkyStaking} from \"src/PinkyStaking.sol\";\nimport {IIntake} from \"src/interfaces/IIntake.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ProofERC20 is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n// Behaves like the live Intake on Robinhood Chain (checked with cast call on 2026-10-09):\n// priceOf(action, IMD) is 0.5 IMD for the sold action and 0, not a revert, for one that is not\n// sold (the oracle-2 action id, or the native asset).\ncontract ProofIntake is IIntake {\n    bytes32 public constant SOLD = bytes32(\"oracle.request@oracle-1\");\n    uint256 public nonce;\n\n    function priceOf(bytes32 action, address) external pure returns (uint256) {\n        return action == SOLD ? 0.5 ether : 0;\n    }\n\n    function request(bytes32, bytes calldata, Callback calldata, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        requestId = keccak256(abi.encode(++nonce));\n    }\n}\n\n/// @notice `make` must refuse a promise while the Intake quotes a price of zero, because `ask`\n/// refuses `price == 0` and `price > terms.maxPrice` (= 0) for ever after, so such a promise can\n/// never be settled and is refunded in full whatever the maker transferred.\n/// Fails on the current code (make succeeds and records maxPrice 0); passes once make reverts.\ncontract ZeroPriceProofTest is Test {\n    ProofERC20 imd;\n    ProofERC20 pinky;\n    ProofERC20 token;\n    ProofIntake intake;\n    PinkyVault vault;\n\n    address owner = makeAddr(\"owner\");\n    address maker = makeAddr(\"maker\");\n\n    function setUp() public {\n        vm.warp(1_790_000_000);\n        vm.roll(1_000);\n        imd = new ProofERC20();\n        pinky = new ProofERC20();\n        token = new ProofERC20();\n        intake = new ProofIntake();\n        PinkyStaking staking = new PinkyStaking(address(pinky), address(imd));\n        vault = new PinkyVault(\n            owner,\n            address(imd),\n            address(staking),\n            address(intake),\n            bytes32(\"oracle.request@oracle-1\"),\n            makeAddr(\"signer\"),\n            5 ether,\n            7,\n            5,\n            86_400\n        );\n        imd.mint(maker, 100 ether);\n        vm.prank(maker);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function test_MakeRefusesAPromiseWhileTheOraclePriceIsZero() public {\n        // The configured action is not (or no longer) sold: the Intake quotes 0.\n        vm.prank(owner);\n        vault.setProtocol(address(intake), bytes32(\"oracle.request@oracle-2\"));\n        assertEq(intake.priceOf(vault.action(), address(imd)), 0);\n\n        // Expected: refused, because no ask() can ever pay for this promise's verdict.\n        // Actual today: accepted with terms[id].maxPrice == 0.\n        vm.prank(maker);\n        vm.expectRevert();\n        vault.make(address(token), 1_000 ether, 1 hours, 10 ether);\n    }\n}","reproduction":"State: vault deployed with the launch.json arguments; owner calls setProtocol(intake, bytes32(\"oracle.request@oracle-2\")) while priceOf(oracle-2, IMD) == 0 (as the live Intake returns today). Call: maker calls make(token, 1000e18, 1 hours, 10e18). Expected: revert, since no ask() can ever pay for this promise. Actual: succeeds; terms[id].maxPrice == 0. Then: owner switches back to oracle-1 (price 0.5 IMD), anyone closes after endTime and waits 32 blocks, watcher calls ask(id) -> InvalidPayment (0.5e18 > maxPrice 0). Under oracle-2 ask(id) -> InvalidPayment (price == 0). After closedAt + 7 days refund(id) returns the full 10 IMD to the maker regardless of what they transferred. Scratch trace: test/scratch/ZeroPricePromise.t.sol; proof: test/scratch/ZeroPriceProof.t.sol fails on the current code with 'next call did not revert as expected'.","severity":"medium","snippet":"        uint256 price = intake.priceOf(action, address(imd));\n        if (bond < minBond || bond < price * MAX_ATTEMPTS) revert BondTooSmall();","title":"make() accepts an oracle price of zero that ask() refuses forever, so the promise can never be settled"},{"citation":"resolved","description":"Boundary x invariant seam introduced by the earlier audit fix. While totalStaked == 0 lastUpdate is frozen, so rewardPerToken() (src/PinkyStaking.sol:67) later adds `(lastTimeRewardApplicable() - lastUpdate) * rewardRate * 1e18 / totalStaked` for the whole idle span in the first update after someone stakes. That update happens on the very next stake/unstake/claim, with totalStaked equal to whatever the arriving staker put in, however small, and however short their stay. The contract's own docstring states the invariant 'a staker who arrives in the block of a payout earns only for the time they stay'; at the totalStaked == 0 boundary it is the opposite: the arriving staker earns for all the time nobody stayed. Whoever watches for totalStaked == 0 during a live stream can stake 1 wei of PINKY, unstake and claim in one transaction, and take every idle second's reward; a staker who arrives one block later and stays the full period gets none of it. The README documents that idle time 'is paid to the next staker', so this is the sharp edge of a stated design rather than a hidden one, but the payout is independent of stake size and duration, which the docstring contradicts and which makes the idle reward a pure MEV prize rather than a staking reward. Impact is bounded by the idle portion of streams (forfeited bonds, 1.575 IMD minimum per broken promise with the launch arguments).\n\nPossible fixes, each a design decision: credit idle time only pro rata to time actually staked (e.g. keep lastUpdate advancing while idle and move the skipped amount into the next notify's leftover), or cap the idle credit by requiring a minimum stake duration before claim.","line":53,"path":"src/PinkyStaking.sol","reproduction":"State: alice stakes 1e18 PINKY; notify(7e18 IMD) (rate 1 IMD/day); after 1 day alice unstakes everything (totalStaked == 0, lastUpdate = day 1); 6 more days pass with nobody staked (stream has ended). Call: attacker holding 1 wei PINKY calls stake(1), unstake(1), claim() in one transaction. Expected (per the contract docstring): the attacker earns only for the time they stayed, i.e. 0. Actual: attacker receives 5999999999999961600 wei IMD (6 IMD less rate truncation); a staker who stakes 100e18 PINKY one second later and stays 7 days has earned(...) == 0. Demonstrated by test/scratch/FlashStakeIdleStream.t.sol (passes on the current code, logging the two balances).","severity":"low","snippet":"        if (totalStaked != 0) lastUpdate = lastTimeRewardApplicable();","title":"The stream that passed with nobody staked is paid in full to the next staker at arrival, so a 1-wei flash stake collects it"},{"citation":"resolved","description":"Outside the assigned math area but concrete. The earlier audit fix makes payout() withhold the 10% bounty when `v.asker == v.maker`, and ask() keeps replacing a maker-asker with the first non-maker asker. Both rules compare addresses, so a maker who intends to break their promise simply calls ask() from any other wallet at endBlock + 32 (they are the party best placed to be first, since they know the promise) and payout() sends them `amount * 1000 / 10000` of their own forfeited bond. The rule as written therefore changes nothing for a maker with two addresses and only penalises a maker who asks from the promising wallet. Net effect: a broken promise forfeits 90%, not 100%, of the remaining bond to the maker, and watchers lose the bounty that was meant to pay them. Fixing it fully is a design question (e.g. pay the bounty only to the first asker after a delay in which the maker had no information edge, or drop the rule and document that the bounty is 10% to whoever asks first); at minimum the README should not claim that a maker 'gets no bounty'.","line":273,"path":"src/PinkyVault.sol","reproduction":"State: maker makes promise id with bond 10e18 (price 0.5e18), closes after endTime. Call: from a second wallet the maker controls, ask(id) at endBlock + 32; the oracle answers out = maxOut + 1 (Broken); anyone calls payout(id). Expected per README/ADAPTATION: the maker receives no bounty. Actual: the second wallet receives (10e18 - 0.5e18) * 1000 / 10000 = 950000000000000000 wei IMD. Demonstrated by test/scratch/SybilBounty.t.sol (passes on the current code).","severity":"low","snippet":"        uint256 bounty = v.asker == v.maker ? 0 : amount * BOUNTY_BPS / 10_000;","title":"The maker-gets-no-bounty rule is keyed on the maker address, so a maker recovers 10% of a broken bond by asking from a second wallet"}],"hash":"528bfc56dc477f689c7a2e71c928e07dd2a7f68fd7be81923115fd760fcfde69","nodeId":"3348f2d7-37bc-4ee8-9916-0eb2dda107c5","outcome":"completed","summary":"The review is complete. The findings file is in place, no tracked file changed, and the scratch tests are excluded by the harness as expected.\n\n**Outcome.** The math, boundary and numerical-gap pass over PinkyVault and PinkyStaking found one medium and two low defects, each with a reproduced trace. The file `.imd-findings.json` holds them plus coverage rows for all 16 listed entry points and two invariants.\n\n**Findings**\n\n- **Medium, PinkyVault.make.** The Intake's `priceOf` returns 0 for an unsold action rather than reverting. I confirmed this live on Robinhood Chain for the oracle-2 action id and for the native asset. `make` accepts that zero, since the floor check degenerates to `bond < 0`, and records `maxPrice = 0`. `ask` then refuses the promise forever, both while the price is 0 and once any price is quoted. The only exit is a full refund seven days after close, so every promise made in that window is unenforceable while still looking bonded. The oracle-consumer reference requires the action id to be switchable for a new version, so the window is ordinary operation. Proof attached: `test/scratch/ZeroPriceProof.t.sol` fails today and passes once `make` rejects a zero price.\n- **Low, PinkyStaking.stake.** The earlier audit fix freezes `lastUpdate` while nobody is staked, so the whole idle stream is credited to the first arriving stake in its first update, regardless of size or stay. A 1-wei stake, unstake and claim in one transaction collected about 6 IMD of a 7 IMD stream in the scratch test, and a staker arriving a second later earned nothing. This contradicts the contract's own docstring, though the README documents the general behaviour.\n- **Low, PinkyVault.payout, outside my area.** The \"maker gets no bounty\" rule compares addresses only. A maker asking from a second wallet receives the 10% bounty on their own broken bond, as the scratch test shows.\n\n**Coverage.** Every entry point has a row. Staking's restretch and reward-per-token arithmetic conserve value up to documented dust, with large headroom given the live IMD supply. The payout split always sums exactly to the remaining bond, the burn transfer and zero-value transfers succeed on the live IMD token, and ArbSys is present on chain 4663 so the block source matches the oracle's. One note without a failing input: the 30-day maximum term is roughly 25 million blocks at the measured cadence, while the live oracle check covered 18 thousand.","treeHash":null,"usage":{"cachedInputTokens":1976506,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":53662,"runtime":"claude","turns":39,"wallClockMs":845246}},{"artifacts":[],"attempt":1,"bundleHash":"f27b41f331e7630238fe1ad76536705ecc0f312a669e412ff59b5610e6d6cd0a","device":"1559912e747bbcd4","findings":[],"hash":"791595ec660550bdc5cbb5ef4b01d265a38665edbc74c34f617818e5ee1ab347","nodeId":"3f4e0118-3f81-4e10-8d06-f1a9432af393","outcome":"completed","summary":"The diff is confined to the findings. Everything is done and verified.\n\n**Both required findings reproduced and are fixed.** The reviewer's proofs failed on the starting tree and pass now.\n\n- **Zero quote at make** (`src/PinkyVault.sol`): `make` now reverts with `InvalidPayment` when the Intake quotes 0, mirroring `ask`. A read-only call to the live Intake confirmed it returns 0, not a revert, for the unsold oracle-2 action.\n- **Price rise strands promises** (`src/PinkyVault.sol`): `Terms` records the Intake and action at `make`. At that same protocol, `ask` accepts any price up to the remaining bond. The old cap now applies only to a different Intake or action the owner set later, which is the owner-redirect case the audit fix was for.\n\n**Advisory findings I judged real and changed** (`src/PinkyStaking.sol`):\n\n- **Flash stake on idle time**: the stream pauses while nobody is staked and resumes for the time it had left. A 1 wei flash stake earns nothing. One earlier test changed with the behaviour.\n- **Dust notify restretch**: a notify never lowers the rate. Dust keeps the current rate and ends sooner. A large payout still raises the rate over seven days.\n\n**Advisory findings answered without code changes:**\n\n- Maker's second wallet bounty: no address rule can exclude it. The README now says the sure penalty is 90% and the bounty is a rebate to the first asker.\n- Split panel refusal: kept deliberately per the oracle-consumer reference, documented as a trade-off.\n- Burn address: verified on the live IMD token via eth_call. The dEaD address holds 28.1 IMD and a simulated transfer returns true.\n- 30-day window and owner trust: documented as a pre-launch check and a stated trust assumption.\n\n**Verification**: `forge build` and `forge test` pass with 53 project tests plus the two proofs. A scratch copy of the protected floor passes on an empty chain 4663 with the launch.json arguments. Constructor signatures are unchanged, so launch.json still applies. All nine findings are answered in `.imd-responses.json`, and ADAPTATION.md has a revision section listing each change.","treeHash":"ed96057c1c4b5d7a11e910c186c16926c08518a5","usage":{"cachedInputTokens":3180103,"inputTokens":866,"model":"claude-fable-5-1","outputTokens":48063,"runtime":"claude","turns":65,"wallClockMs":870613}},{"artifacts":[],"attempt":2,"bundleHash":"6f4487625eed3f7f24afa9e66f47e76cd8981b7e4db3e56a3ad23efa13033f58","device":"f009de0ce52c7b3a","findings":[],"hash":"b952fd8456fac332552be186063577731b2c48847547bdae840bebb2a4c2ae5e","nodeId":"6bb66b88-b3af-44ac-b3da-568aa1a6d57d","outcome":"completed","summary":"launch.json is updated and verified. The previous attempt was rejected because it left the manifest untouched, so this round rewrites the notes to describe the tree as the last accepted round left it, while the contract list and constructor arguments stay exactly as accepted.\n\n**What changed**\n\n- Only the notes field of launch.json. The contracts, their constructor words, the token block and the pool block are byte for byte what was accepted before.\n- The notes now describe the accepted behaviour from the last code round: the bond must cover three oracle fees and is refused while the Intake quotes no price, each promise records the Intake, action and price it was made under so a later owner-set Intake is capped at that price, the panel a request was bought with is what the answer is checked against, and the staking stream pauses while nobody is staked and never slows on a small addition.\n- The notes also spell out the full promise lifecycle and the broken-bond split, say the owner cannot move funds, and stay well under the limit at 2,670 characters.\n\n**What I verified**\n\n- PinkyStaking takes two address parameters and PinkyVault takes ten parameters, matching the manifest's argument counts and declaration order.\n- A scratch test in test/scratch deploys LaunchToken, PinkyStaking and PinkyVault on an empty chain 4663 from a code-less factory address with the manifest's literal words, reads every setting back, and confirms the supply stays with the factory and both runtimes fit the size limit.\n- A schema check on launch.json passed: only the five allowed keys, lowercase addresses, backward-only contract references, address references only in address slots, exact pool values, ASCII notes.\n\n```\nforge build: ok\nforge test:  54 passed, 0 failed (6 suites, including the scratch manifest test)\ngit diff --stat: launch.json | 2 +-\n```\n\nNo file other than launch.json is changed; the scratch test lives in test/scratch and is discarded before submission.","treeHash":"1362dc1cf60284d0ff20756f8bda78da16e1b2eb","usage":{"cachedInputTokens":606675,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":8047,"runtime":"claude","turns":11,"wallClockMs":279506}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"559cfaaab2c0d013","findings":[{"citation":"resolved","description":"The revision settles my earlier medium finding (d313e09c) in the first shape it offered: ask() at the Intake and action recorded at make() accepts any price up to what is left of the bond, and the maxPrice cap applies only to another Intake or action set since. Read as new code, the rule moves the owner-Intake bound: before the fix an owner-controlled Intake, whenever it was set, could collect at most MAX_ATTEMPTS x the quote seen at make; now an Intake the owner sets before a promise is made is that promise's trusted protocol and may raise its price to v.bond and take it all in one ask (ask's only ceiling there is `price > v.bond`). The owner is already trusted to the same degree through setSigner (my earlier info c24a0d72, which the author kept as a documented trust assumption), the maker can read `intake()` before making, and the launch sets the real Intake, so this is not a new unprivileged path and not a defect in the rule itself, which is the one I proposed. It is reported so the README's 'Owner' item (README.md line 80: 'The owner cannot redirect a bond through an Intake of their own: another Intake or action is never charged more per request than the price quoted when the promise was made, at most three times') is narrowed to promises made before the owner changed the Intake, and so the launch's statement of who holds $owner covers it. No code change is needed; a sentence in the README is the remedy.","line":218,"path":"src/PinkyVault.sol","reproduction":"test/scratch/OwnerIntake.t.sol (passes on this tree, asserting the outcome): vault deployed with the launch arguments against a mock Intake quoting 0.5 IMD. 1) owner calls setProtocol(greedy, action) where greedy is a MockIntake paying the owner, price 0.5 IMD. 2) maker calls make(token, 1000e18, 1 hours, 10e18): accepted, terms(id).intake == greedy, maxPrice 0.5e18. 3) warp +1 hour, roll +36000, close(id), roll +32. 4) greedy.setPrice(10e18); anyone calls ask(id). Expected per README line 80: the owner's Intake is charged at most 0.5 IMD per request, three times. Actual: ask succeeds, promises(id).bond == 0 and the owner's IMD balance is 10e18; the maker's whole bond went to the owner in one call. Control (test_IntakeSetAfterMakeIsStillCapped): the same switch made after make() is refused with InvalidPayment at 0.5e18 + 1, as the README says.","severity":"info","snippet":"        bool sameProtocol = address(intake_) == t.intake && action_ == t.action;\n        if (!sameProtocol && price > t.maxPrice) revert InvalidPayment();","title":"Trust assumption widened by the price-rise fix: an Intake the owner sets before a promise is made can take the whole bond in one ask, so the README's 'cannot redirect a bond through an Intake of their"}],"hash":"c22c12609e6dbddea52a97a8c3fe273c3701873cc224039765d8fe7253f2517e","nodeId":"9c3ab10e-6d5f-4e17-b11d-b02528664863","outcome":"completed","summary":"The review is complete. The findings file is written and the scratch directory is removed.\n\n**Outcome: both reproducible findings from the last round are fixed, and nothing new blocks the launch.**\n\nSettlement of what I reported before:\n\n- **Zero quote accepted by make (medium, fixed).** `make` now reverts with `InvalidPayment` when the Intake quotes 0, mirroring `ask`. My proof and the two specialists' proofs all pass on this tree.\n- **Price rise strands open promises (medium, fixed).** `Terms` records the Intake and action at `make`. At that same protocol `ask` pays any price up to the remaining bond. Other Intakes or actions stay capped at the recorded price. My proof passes, and the author's three tests cover both sides.\n- **Flash stake collects idle stream (low, fixed).** The stream now pauses while nobody is staked and resumes for the time it had left. I traced `_resume` for underflow and double-pay paths and found none. The invariant suite ran 16,384 calls with zero reverts.\n- **Dust notify restretches the stream (info, fixed).** A notify never lowers the rate. The new rate times the new period never exceeds what the contract holds.\n- **Maker's second wallet takes the bounty (low, documented).** Code unchanged. The README now describes the bounty as a 10% rebate to whoever asks first, which was the minimum remedy I offered. Settled.\n- **Split panel, window length, dEaD burn, owner trust (info).** Documented or checked live by the author. The author read the live IMD token and confirmed transfers to the burn address succeed. Settled.\n\nOne new info-level note, not blocking:\n\n- **The price-rise fix widens what an owner-set Intake can take.** An Intake the owner configures before a promise is made becomes that promise's trusted protocol and can raise its price to the whole bond in one `ask`. Before the fix it was capped at three fees. The owner is already trusted to the same degree through `setSigner`, so this is a trust assumption, not a defect. The README sentence at line 80 saying the owner cannot redirect a bond through their own Intake now holds only for promises made before the switch. A scratch test reproduced both the drain and the still-capped after-make case.\n\nCoverage answers all 16 entry points plus two invariant rows. The project's full suite passes with 137 tests. The launch manifest matches both constructors in argument count and order. The only open item for the launch operator remains the pre-launch live check of a multi-day oracle window, which cannot be run from a contributor task.","treeHash":null,"usage":{"cachedInputTokens":699055,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":15512,"runtime":"claude","turns":16,"wallClockMs":361504}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"232b62e021f6f394","findings":[{"citation":"resolved","description":"`ask` refuses any quote above `terms[id].maxPrice`, the price seen at `make`. That cap was added so the owner could not route a bond through a greedy Intake via `setProtocol`, but it also fires when the real Intake (unchanged, trusted, and documented as able to move its price without a redeploy) raises the action's price by any amount. Once that happens no one can buy a verdict for any promise made under the old price: the only remaining transition is `refund`, which after REFUND_GRACE returns the whole remaining bond to the maker whether or not they dumped. The protocol's core guarantee (\"break it and the IMD does not come home\") then fails for the entire open book at once, and a maker who sees a price rise announced can dump freely for the rest of the term. Economic size: every bond outstanding at the moment of the rise (min 5 IMD each, no upper bound) returns to its maker; watchers earn no bounty and stakers no stream. The README documents this as a tradeoff, but a per-promise cap keyed to the quote alone over-reaches: the earlier concern was the owner changing the Intake, not the protocol moving its price. Minimal fix that preserves both: keep the owner-redirect protection by recording the Intake address at `make` and refusing a different Intake above `maxPrice`, while at the same Intake allow a price up to the bond (as `make` already sized it for 3 attempts) or let the asker top up `price - maxPrice` from their own wallet (a watcher chasing a 10% bounty will). Either shape makes the attached test pass.","line":201,"path":"src/PinkyVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PinkyVault} from \"src/PinkyVault.sol\";\nimport {PinkyStaking} from \"src/PinkyStaking.sol\";\nimport {IIntake} from \"src/interfaces/IIntake.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ScratchERC20 is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @dev The live Intake: pulls exactly `amount`, hands back an id. Its price is owner-settable.\ncontract ScratchIntake is IIntake {\n    uint256 public price;\n    uint256 public nonce;\n\n    constructor(uint256 price_) {\n        price = price_;\n    }\n\n    function setPrice(uint256 price_) external {\n        price = price_;\n    }\n\n    function priceOf(bytes32, address) external view returns (uint256) {\n        return price;\n    }\n\n    function request(bytes32, bytes calldata, Callback calldata, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        require(amount >= price, \"PriceNotMet\");\n        IERC20(asset).transferFrom(msg.sender, address(0xFEE), amount);\n        requestId = keccak256(abi.encode(address(this), ++nonce));\n    }\n}\n\n/// @notice A broken promise must stay settleable when the Intake's price moves up during the term.\n/// On the code as it is, `ask` refuses any price above the one seen at `make`, so the only path\n/// left is `refund`, which hands the whole bond back to the maker whatever they did.\ncontract PriceRiseStrandsVerdictTest is Test {\n    uint256 constant PRICE = 0.5 ether;\n    uint256 constant BOND = 10 ether;\n\n    ScratchERC20 imd;\n    ScratchERC20 pinky;\n    ScratchERC20 meme;\n    ScratchIntake intake;\n    PinkyStaking staking;\n    PinkyVault vault;\n\n    address maker = makeAddr(\"maker\");\n    address watcher = makeAddr(\"watcher\");\n\n    function setUp() public {\n        vm.warp(1_790_000_000);\n        vm.roll(1_000);\n        imd = new ScratchERC20();\n        pinky = new ScratchERC20();\n        meme = new ScratchERC20();\n        intake = new ScratchIntake(PRICE);\n        staking = new PinkyStaking(address(pinky), address(imd));\n        vault = new PinkyVault(\n            makeAddr(\"owner\"),\n            address(imd),\n            address(staking),\n            address(intake),\n            bytes32(\"oracle.request@oracle-1\"),\n            vm.addr(0xA11CE),\n            5 ether,\n            7,\n            5,\n            86_400\n        );\n        imd.mint(maker, BOND);\n        imd.mint(watcher, 1 ether);\n        vm.prank(maker);\n        imd.approve(address(vault), type(uint256).max);\n        vm.prank(watcher);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function test_APriceRiseAtTheSameIntakeMustNotStrandTheVerdict() public {\n        vm.prank(maker);\n        uint256 id = vault.make(address(meme), 1_000 ether, 1 hours, BOND);\n\n        // The protocol moves the action's price from 0.5 to 0.6 IMD during the term.\n        intake.setPrice(PRICE + 0.1 ether);\n\n        vm.warp(vm.getBlockTimestamp() + 1 hours);\n        vm.roll(vm.getBlockNumber() + 36_000);\n        vault.close(id);\n        vm.roll(vm.getBlockNumber() + vault.SETTLE_DELAY_BLOCKS());\n\n        // The bond (10 IMD) covers the new price many times over and the watcher has approved the\n        // vault for the difference, yet no one can buy the verdict.\n        vm.prank(watcher);\n        try vault.ask(id) {}\n        catch {\n            // Reproduce the consequence: a week later the maker takes the whole bond back, so a\n            // maker who dumped during the term is never judged.\n            vm.warp(vm.getBlockTimestamp() + vault.REFUND_GRACE());\n            vault.refund(id);\n            assertEq(imd.balanceOf(maker), BOND, \"the maker got every wei back without a verdict\");\n            assertTrue(false, \"ask reverted after a 0.1 IMD price rise; the promise can only be refunded\");\n        }\n\n        (,,,,,,,,,, PinkyVault.Status status,,,,,) = vault.promises(id);\n        assertEq(uint8(status), uint8(PinkyVault.Status.Asked), \"the verdict was bought\");\n    }\n}","reproduction":"State: Intake quotes 0.5 IMD. maker calls make(meme, 1000e18, 1 hours, 10e18) -> terms[1].maxPrice = 0.5e18. The Intake moves the price to 0.6 IMD (intake.setPrice(0.6e18) in the mock; on Robinhood Chain the Intake owner's price change). warp +1h, close(1), roll +32. watcher calls ask(1): expected a request is bought (the bond holds 9.5 IMD and the watcher is willing to pay); actual revert InvalidPayment because price (0.6e18) > maxPrice (0.5e18). Every retry reverts identically. warp +7 days: refund(1) succeeds and the maker's IMD balance is back to 10e18 with no verdict ever issued. The attached test test/scratch/PriceRiseStrandsVerdict.t.sol fails on this tree with 'ask reverted after a 0.1 IMD price rise; the promise can only be refunded'.","severity":"medium","snippet":"        if (price == 0 || price > v.bond || price > t.maxPrice) revert InvalidPayment();","title":"A rise in the Intake's price during a promise's term makes every open promise unsettleable, so a broken maker is refunded in full"},{"citation":"resolved","description":"The README and ADAPTATION fix state that a maker who asks about their own broken promise earns no bounty. The check compares addresses only. A maker who knows they broke the promise (they always do) also knows the exact block `ask` opens (endBlock + 32), so they call `ask` from any other wallet in that block and `v.asker` becomes that wallet; `payout` then pays it 10% of the remaining bond. The maker's loss drops from 100% to 90% of the bond, and because the maker is best placed to be first, honest watchers who monitor for broken promises are front-run out of the bounty that is meant to fund them. There is no on-chain fix that distinguishes an alt wallet; the honest statement is that the rule is cosmetic and the bounty is a fixed 10% rebate the maker can usually capture. Options: drop the special case and document it, or make the bounty a function of time since `ask` opened (e.g. first N blocks pay nothing) so a watcher has a window the maker cannot pre-empt.","line":273,"path":"src/PinkyVault.sol","reproduction":"maker: make(meme, 1000e18, 1 hours, 10e18) -> id 1. warp +1h, roll +36000, close(1), roll +32. alt (a fresh wallet funded with gas only) calls ask(1). Oracle answers 1000e18 + 1 (Broken). payout(1): expected (per README) nothing of the bond returns to the maker's side; actual alt receives 950000000000000000 wei (10% of 9.5 IMD), as test_MakerAltWalletCollectsTheBounty in test/scratch/Leads.t.sol logs.","severity":"low","snippet":"        uint256 bounty = v.asker == v.maker ? 0 : amount * BOUNTY_BPS / 10_000;","title":"The no-bounty-for-the-maker rule is keyed on the asker address, so a maker asks from a second wallet and keeps 10% of a forfeited bond"},{"citation":"resolved","description":"`make` reads `intake.priceOf(action, imd)` and only uses it for the `bond >= 3 * price` floor and to store `terms[id].maxPrice`. If the quote is 0 (the action id set by the owner is not sold on this Intake, the Intake is not yet live for that action, or the protocol makes the action free) `make` still succeeds, stores maxPrice = 0, and then `ask` reverts forever: a later non-zero quote fails `price > t.maxPrice`, and a zero quote fails `price == 0`. The bond is locked for duration + REFUND_GRACE and the promise can only be refunded, so a broken maker is never judged and every promise made in that state is a dead promise. The symmetric guard belongs in `make`: revert when `price == 0` so the vault does not sell a promise it cannot settle (and so a misconfigured `action` after `setProtocol` is noticed at the first `make` rather than at the first `ask`).","line":155,"path":"src/PinkyVault.sol","reproduction":"intake.setPrice(0) (equivalently: owner setProtocol(intake, bytes32('some.unsold.action')) on the live Intake, where priceOf returns 0). maker: make(meme, 1000e18, 1 hours, 10e18) succeeds; terms(1).maxPrice == 0. warp +1h, roll +36000, close(1), roll +32. intake.setPrice(0.5e18): ask(1) reverts InvalidPayment. intake.setPrice(0): ask(1) reverts InvalidPayment. warp +7 days: refund(1) returns 10e18 to the maker. test_ZeroPriceMakesAnUnsettleablePromise in test/scratch/Leads.t.sol passes on this tree showing exactly this sequence. Expected: make reverts when the quote is zero.","severity":"low","snippet":"        if (bond < minBond || bond < price * MAX_ATTEMPTS) revert BondTooSmall();","title":"make accepts a promise while the Intake quotes zero, recording maxPrice = 0 so the promise can never be asked and always refunds"},{"citation":"resolved","description":"`notify` is permissionless and every call spreads the unpaid remainder over a fresh DURATION from now. The MIN_REWARD floor of 0.01 IMD is the only cost. A griefer who calls `notify(0.01 ether)` once a day keeps the stream's rate decaying by 6/7 per day: after the 7 days the README advertises, stakers have received about 66% of the payout instead of 100%, and the tail stretches indefinitely (about 88% after 14 days, 99% after 30). Nothing is lost permanently, but a staker who leaves on day 7 expecting the stream to have ended forfeits the rest to later stakers, and the attacker can time their own stake to collect it. Cost to the attacker: 0.07 IMD per week per stream, against streams that are half of every forfeited bond (minimum 1.575 IMD, no upper bound). This is the known Synthetix `notifyRewardAmount` extension issue; the code comment accepts it with the floor. If the requester wants the seven-day promise to hold, restrict `notify` to the vault (the only intended caller) or only extend `periodFinish` when the added amount is at least a fraction of what is left.","line":106,"path":"src/PinkyStaking.sol","reproduction":"staker stakes 1000e18 PINKY. notify(22e18) from any account (the vault's payout path does the same). Then for d in 1..7: warp +1 day; griefer calls notify(0.01e18). After day 7: staking.earned(staker) = 14545627271434941000 wei (about 14.5 IMD) versus 22e18 expected if the stream had run its advertised seven days untouched; test_NotifyRestretchSlowsTheStream in test/scratch/Leads.t.sol logs both numbers.","severity":"low","snippet":"            rewardRate = (amount + (periodFinish - block.timestamp) * rewardRate) / DURATION;","title":"Anyone can restretch the stakers' IMD stream with 0.01 IMD per call, cutting what stakers receive in the advertised seven days by a third"},{"citation":"resolved","description":"The vault lets a maker choose any duration up to 30 days and the body pins `window: {fromBlock, toBlock}` to the exact block range. The README's live check covered 1,000 blocks (tools/oracle-test.calldata.json: 84167000 to 84168000) and says ranges of about 18,000 blocks (half an hour) have been attested; at that block rate a 30-day term is roughly 26 million blocks of Transfer logs for the panel to sum. If the oracle refuses or fails such a request (status 1 or 2, no callback), the promise can only be refunded after three paid attempts or REFUND_GRACE, so a maker who wants to dump without penalty picks the longest term. This cannot be demonstrated against a mock and the reference does not state an explicit block-range cap (it allows relative windows of up to 720 hours), so it is recorded as a lead for the requester to verify with one live request at a long range before launch; if the oracle has a limit, cap MAX_DURATION to it.","line":93,"path":"src/PinkyVault.sol","reproduction":"make(token, maxOut, 30 days, 5e18) on Robinhood Chain; close after 30 days; ask. Expected: a verdict within ANSWER_TIMEOUT. Unverified: whether the oracle answers a log-sum over the resulting multi-million-block window. Offline the vault accepts the input and builds the body (bodyOf(id)) without error.","severity":"info","snippet":"    uint256 public constant MAX_DURATION = 30 days;","title":"MAX_DURATION of 30 days produces block windows two to three orders of magnitude beyond the only range the oracle has been shown to answer (lead, not reproducible offline)"}],"hash":"c4394d5ba6f208bb960515c2bbb521f71bc41923b5191ee7850f156c16ceb372","nodeId":"375df25f-d344-4685-a4b2-bfbfde8005b8","outcome":"completed","summary":"The findings file is in place at the repository root (the harness excludes it and the scratch directory from git, which is expected for a review). No tracked file was changed. The review is complete.\n\n**Outcome:** `.imd-findings.json` holds 5 findings and 19 coverage rows for the Economic Security, Invariant and Flow Gap area. One finding is medium and carries a failing Foundry proof; three are low; one is an info-level lead that cannot be verified offline.\n\n**Findings, by severity:**\n\n- **Medium, `ask` price cap (src/PinkyVault.sol:201).** `ask` refuses any Intake quote above the one recorded at `make`. When the real Intake raises the price by any amount, no open promise can ever be asked, and after the 7-day grace `refund` returns the whole bond to the maker regardless of whether they dumped. The protocol's core guarantee fails for the entire open book at once. The proof test in test/scratch/PriceRiseStrandsVerdict.t.sol fails on this tree. The proposed fix keeps the owner-redirect protection by recording the Intake at `make`, while allowing a same-Intake price up to the bond or an asker top-up.\n- **Low, bounty rule bypass (line 273).** The \"maker gets no bounty\" rule keys on the asker address, so a maker asks from a second wallet and keeps 10% of a forfeited bond, front-running honest watchers. Logged: 0.95 IMD on a 10 IMD bond.\n- **Low, zero quote at `make` (line 155).** A zero Intake quote (unsold action id, or a free action) is accepted and stored as maxPrice 0, so the promise can never be asked and always refunds.\n- **Low, staking restretch (src/PinkyStaking.sol:106).** Permissionless `notify` with 0.01 IMD per day stretches the stream so stakers receive about 14.5 of 22 IMD within the advertised seven days.\n- **Info, 30-day windows.** A 30-day term yields a block window two to three orders of magnitude beyond the 1,000-block range the live check answered. Recorded as a lead for one live request before launch.\n\n**What held:** the vault's bond conservation across make, ask, payout and refund; the staking gap-credit accounting, confirmed by a scratch solvency invariant over 1,649 handler calls; the callback at 89,364 gas from cold under the 200k stipend; the payout `try` path, which cannot be gas-griefed into burning the staker share; and the launch manifest's argument order, live addresses and owner placeholder.\n\n**Not reached:** behaviour against the live oracle (window-size limits, callback delivery on chain), which no offline test can establish.","treeHash":null,"usage":{"cachedInputTokens":1471767,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":44342,"runtime":"claude","turns":29,"wallClockMs":750197}},{"artifacts":[],"attempt":1,"bundleHash":"bbc41a0b89152fae672be9720aacbb7a9687436aebc359e65b6d72d0aa721d8b","device":"589ef002581a5371","findings":[],"hash":"dfd64ac9d3e0226f8b2480b1a4c1c3d6bb5167f2a2b7419f604a9230586b210a","nodeId":"6bb66b88-b3af-44ac-b3da-568aa1a6d57d","outcome":"completed","summary":"launch.json is written and verified. The build and all 46 tests pass, and the manifest is the only changed file.\n\n**What the manifest declares**\n\n- Token: LaunchToken, name Pinky, symbol PINKY, 18 decimals, no constructor arguments. This matches the accepted source.\n- PinkyStaking: two address arguments, the launch token via `$token` and IMD as the reward token. It has no owner, so no `$owner` is passed.\n- PinkyVault: ten arguments in declaration order. Owner from policy via `$owner`, IMD, `$contract:PinkyStaking`, the Robinhood Chain Intake, the padded action id for oracle.request@oracle-1, the oracle signer, a 5 IMD minimum bond, panel 7, quorum 5, and 86400 seconds of answer validity.\n- Pool: IMD as the paired currency, fee 12500, tick spacing 60, and the one-to-one initial price, exactly as the launch terms give them.\n\n**How it was checked**\n\n- The Intake, signer, IMD and action id values match the live Robinhood Chain table in the oracle-consumer reference.\n- A scratch Foundry test deployed all three contracts on an empty chain 4663 from a factory address with the exact manifest arguments and confirmed every stored setting, the supply staying with the deployer, and runtime sizes under the EIP-170 limit.\n- A script checked the manifest keys, string and number types, lowercase addresses, name lengths and uniqueness, one argument per constructor parameter from the compiled ABI, backward-only references in address slots, and the notes length.\n\nThe previous manifest from the earlier contributor already carried these arguments. The only substantive change is the notes field, which now spells out each constructor parameter, the owner's setters, and that no contract receives launch supply.","treeHash":"ac0f5a176592015dadf4a22ccccebd327908ec6f","usage":{"cachedInputTokens":545956,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":8654,"runtime":"claude","turns":19,"wallClockMs":241319}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"39da99ded7f125c8","findings":[{"citation":"resolved","description":"make reads intake.priceOf(action, imd) and uses it only as the bond floor (bond >= 3 * price, which never fires at 0) and as the per-promise cap terms[id].maxPrice. ask (src/PinkyVault.sol:201) reverts InvalidPayment on price == 0 and on price > t.maxPrice, so a promise recorded with maxPrice == 0 cannot be asked under any later quote or owner setting. Its only exit is refund after REFUND_GRACE, which returns the whole bond to the maker whatever they transferred, so the vault's guarantee is void for every promise made in that state. The zero quote is reachable without a malicious actor: the owner stages the next action version (the oracle-consumer reference says oracle-2 must be adoptable without a redeploy) before the Intake sells it, or sets a mistyped action id, or the Intake retires oracle-1. Three specialists (permissions, math, economics) and the test writer reported the same root cause; merged here at the math specialist's severity. I could not reach the Robinhood RPC from this machine, so the specialists' claim that the live Intake returns 0 rather than reverting for an unsold action is not independently confirmed; the mock in the attached proof assumes it. Fix: in make, revert (InvalidPayment or BondTooSmall) when price == 0, mirroring ask. Entry validation then matches settlement validation and the launch configuration (oracle-1 at 0.5 IMD) is unaffected.","line":155,"path":"src/PinkyVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PinkyVault} from \"src/PinkyVault.sol\";\nimport {PinkyStaking} from \"src/PinkyStaking.sol\";\nimport {IIntake} from \"src/interfaces/IIntake.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ProofERC20 is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n// Behaves like the live Intake on Robinhood Chain (checked with cast call on 2026-10-09):\n// priceOf(action, IMD) is 0.5 IMD for the sold action and 0, not a revert, for one that is not\n// sold (the oracle-2 action id, or the native asset).\ncontract ProofIntake is IIntake {\n    bytes32 public constant SOLD = bytes32(\"oracle.request@oracle-1\");\n    uint256 public nonce;\n\n    function priceOf(bytes32 action, address) external pure returns (uint256) {\n        return action == SOLD ? 0.5 ether : 0;\n    }\n\n    function request(bytes32, bytes calldata, Callback calldata, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        IERC20(asset).transferFrom(msg.sender, address(this), amount);\n        requestId = keccak256(abi.encode(++nonce));\n    }\n}\n\n/// @notice `make` must refuse a promise while the Intake quotes a price of zero, because `ask`\n/// refuses `price == 0` and `price > terms.maxPrice` (= 0) for ever after, so such a promise can\n/// never be settled and is refunded in full whatever the maker transferred.\n/// Fails on the current code (make succeeds and records maxPrice 0); passes once make reverts.\ncontract ZeroPriceProofTest is Test {\n    ProofERC20 imd;\n    ProofERC20 pinky;\n    ProofERC20 token;\n    ProofIntake intake;\n    PinkyVault vault;\n\n    address owner = makeAddr(\"owner\");\n    address maker = makeAddr(\"maker\");\n\n    function setUp() public {\n        vm.warp(1_790_000_000);\n        vm.roll(1_000);\n        imd = new ProofERC20();\n        pinky = new ProofERC20();\n        token = new ProofERC20();\n        intake = new ProofIntake();\n        PinkyStaking staking = new PinkyStaking(address(pinky), address(imd));\n        vault = new PinkyVault(\n            owner,\n            address(imd),\n            address(staking),\n            address(intake),\n            bytes32(\"oracle.request@oracle-1\"),\n            makeAddr(\"signer\"),\n            5 ether,\n            7,\n            5,\n            86_400\n        );\n        imd.mint(maker, 100 ether);\n        vm.prank(maker);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function test_MakeRefusesAPromiseWhileTheOraclePriceIsZero() public {\n        // The configured action is not (or no longer) sold: the Intake quotes 0.\n        vm.prank(owner);\n        vault.setProtocol(address(intake), bytes32(\"oracle.request@oracle-2\"));\n        assertEq(intake.priceOf(vault.action(), address(imd)), 0);\n\n        // Expected: refused, because no ask() can ever pay for this promise's verdict.\n        // Actual today: accepted with terms[id].maxPrice == 0.\n        vm.prank(maker);\n        vm.expectRevert();\n        vault.make(address(token), 1_000 ether, 1 hours, 10 ether);\n    }\n}","reproduction":"State: vault with the launch.json arguments; Intake.priceOf(action, IMD) == 0 (owner called setProtocol(intake, bytes32('oracle.request@oracle-2')) while that action is unsold, or the price is 0). Call: maker calls make(token, 1000e18, 1 hours, 10e18). Expected: revert, since no ask can ever pay for this promise. Actual: succeeds, 10 IMD pulled, terms(id).maxPrice == 0. Then: quote returns to 0.5 IMD (owner switches back to oracle-1); after endTime anyone calls close(id); 32 blocks later ask(id) reverts InvalidPayment (0.5e18 > maxPrice 0); under a zero quote ask(id) also reverts InvalidPayment (price == 0). After closedAt + 7 days refund(id) returns the full 10 IMD to the maker regardless of what they moved. Reproduced: .imd/reads/proofs/Proof_86bb4d54ee7e.t.sol and Proof_10b5e010dcc5.t.sol both fail on this tree (run from test/scratch: 'next call did not revert as expected' and 'make accepted a bond under a zero quote and ask can never pay for it').","severity":"medium","snippet":"        if (bond < minBond || bond < price * MAX_ATTEMPTS) revert BondTooSmall();","title":"make() accepts a promise while the Intake quotes 0, which ask() refuses forever: the promise can only be refunded in full"},{"citation":"resolved","description":"ask refuses any quote above terms[id].maxPrice, the price seen at make. That cap was added (ADAPTATION.md) so the owner could not route a bond through a greedy Intake via setProtocol, but it fires equally when the unchanged, trusted Intake raises the action's price by any amount, which the oracle-consumer reference says is expected ('a moved price must not need a redeploy'). From that moment no one can buy a verdict for any promise made under the old price: the only remaining transition is refund, which after REFUND_GRACE returns the whole remaining bond to the maker whether or not they dumped, and a maker who sees the rise announced can dump freely for the rest of the term. Every bond outstanding at the rise (minimum 5 IMD, no upper bound) goes back to its maker; watchers earn no bounty and stakers no stream. The README documents this as a known limit, but it voids the core guarantee for the whole open book on an event outside anyone's control here, so it is reported as a defect rather than a trust assumption. Fix that keeps the owner-redirect protection: record the Intake address and action id at make, keep the maxPrice cap only when ask would pay a different Intake or action, and at the same (intake, action) allow any price up to v.bond (make already sized the bond for three attempts); or let the asker top up price - maxPrice from their own wallet. The attached proof passes under either shape.","line":201,"path":"src/PinkyVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PinkyVault} from \"src/PinkyVault.sol\";\nimport {PinkyStaking} from \"src/PinkyStaking.sol\";\nimport {IIntake} from \"src/interfaces/IIntake.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ScratchERC20 is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @dev The live Intake: pulls exactly `amount`, hands back an id. Its price is owner-settable.\ncontract ScratchIntake is IIntake {\n    uint256 public price;\n    uint256 public nonce;\n\n    constructor(uint256 price_) {\n        price = price_;\n    }\n\n    function setPrice(uint256 price_) external {\n        price = price_;\n    }\n\n    function priceOf(bytes32, address) external view returns (uint256) {\n        return price;\n    }\n\n    function request(bytes32, bytes calldata, Callback calldata, address asset, uint256 amount)\n        external\n        payable\n        returns (bytes32 requestId)\n    {\n        require(amount >= price, \"PriceNotMet\");\n        IERC20(asset).transferFrom(msg.sender, address(0xFEE), amount);\n        requestId = keccak256(abi.encode(address(this), ++nonce));\n    }\n}\n\n/// @notice A broken promise must stay settleable when the Intake's price moves up during the term.\n/// On the code as it is, `ask` refuses any price above the one seen at `make`, so the only path\n/// left is `refund`, which hands the whole bond back to the maker whatever they did.\ncontract PriceRiseStrandsVerdictTest is Test {\n    uint256 constant PRICE = 0.5 ether;\n    uint256 constant BOND = 10 ether;\n\n    ScratchERC20 imd;\n    ScratchERC20 pinky;\n    ScratchERC20 meme;\n    ScratchIntake intake;\n    PinkyStaking staking;\n    PinkyVault vault;\n\n    address maker = makeAddr(\"maker\");\n    address watcher = makeAddr(\"watcher\");\n\n    function setUp() public {\n        vm.warp(1_790_000_000);\n        vm.roll(1_000);\n        imd = new ScratchERC20();\n        pinky = new ScratchERC20();\n        meme = new ScratchERC20();\n        intake = new ScratchIntake(PRICE);\n        staking = new PinkyStaking(address(pinky), address(imd));\n        vault = new PinkyVault(\n            makeAddr(\"owner\"),\n            address(imd),\n            address(staking),\n            address(intake),\n            bytes32(\"oracle.request@oracle-1\"),\n            vm.addr(0xA11CE),\n            5 ether,\n            7,\n            5,\n            86_400\n        );\n        imd.mint(maker, BOND);\n        imd.mint(watcher, 1 ether);\n        vm.prank(maker);\n        imd.approve(address(vault), type(uint256).max);\n        vm.prank(watcher);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function test_APriceRiseAtTheSameIntakeMustNotStrandTheVerdict() public {\n        vm.prank(maker);\n        uint256 id = vault.make(address(meme), 1_000 ether, 1 hours, BOND);\n\n        // The protocol moves the action's price from 0.5 to 0.6 IMD during the term.\n        intake.setPrice(PRICE + 0.1 ether);\n\n        vm.warp(vm.getBlockTimestamp() + 1 hours);\n        vm.roll(vm.getBlockNumber() + 36_000);\n        vault.close(id);\n        vm.roll(vm.getBlockNumber() + vault.SETTLE_DELAY_BLOCKS());\n\n        // The bond (10 IMD) covers the new price many times over and the watcher has approved the\n        // vault for the difference, yet no one can buy the verdict.\n        vm.prank(watcher);\n        try vault.ask(id) {}\n        catch {\n            // Reproduce the consequence: a week later the maker takes the whole bond back, so a\n            // maker who dumped during the term is never judged.\n            vm.warp(vm.getBlockTimestamp() + vault.REFUND_GRACE());\n            vault.refund(id);\n            assertEq(imd.balanceOf(maker), BOND, \"the maker got every wei back without a verdict\");\n            assertTrue(false, \"ask reverted after a 0.1 IMD price rise; the promise can only be refunded\");\n        }\n\n        (,,,,,,,,,, PinkyVault.Status status,,,,,) = vault.promises(id);\n        assertEq(uint8(status), uint8(PinkyVault.Status.Asked), \"the verdict was bought\");\n    }\n}","reproduction":"State: Intake quotes 0.5 IMD; maker calls make(meme, 1000e18, 1 hours, 10e18) -> terms(1).maxPrice == 0.5e18. The Intake's price moves to 0.6 IMD (intake.setPrice(0.6e18) in the mock; on Robinhood Chain the Intake owner's price change). warp +1 hour, close(1), roll +32. watcher calls ask(1). Expected: a request is bought (the bond holds 10 IMD, far above 0.6). Actual: revert InvalidPayment because 0.6e18 > maxPrice 0.5e18; every retry reverts identically. warp +7 days: refund(1) succeeds and the maker's IMD balance is back to 10e18 with no verdict ever issued. Reproduced: .imd/reads/proofs/Proof_556207f62508.t.sol fails on this tree with 'ask reverted after a 0.1 IMD price rise; the promise can only be refunded'.","severity":"medium","snippet":"        if (price == 0 || price > v.bond || price > t.maxPrice) revert InvalidPayment();","title":"A price rise at the Intake during a promise's term makes every open promise unaskable, so a broken maker is refunded in full"},{"citation":"resolved","description":"payout withholds the 10% bounty only when v.asker == v.maker, and ask (src/PinkyVault.sol:207, 'if (v.asker == address(0) || v.asker == v.maker) v.asker = msg.sender;') records whoever asks first. A maker who broke the promise knows endBlock exactly (they can call close themselves) and asks at endBlock + 32 from any other wallet, which is then paid amount * 1000 / 10000 of their own forfeited bond; stakers and the burn receive 90% of the remainder instead of the 100% the README and ADAPTATION.md promise. The replacement rule in ask only runs when no verdict lands within ANSWER_TIMEOUT (1 day); the live oracle answered in 2 min 15 s, so in the normal case it never runs, which also lets a maker who asks first from the promising wallet deny a watcher's bounty at no cost (the watcher's ask in the same block reverts TooEarly). Reported by the permissions, math and flow specialists; merged. Impact is bounded to 10% of each forfeited bond and the maker still loses the other 90%, hence low. No address comparison can exclude a second wallet. Options: drop the special case and document the bounty as a 10% rebate to whoever asks first; or pay the bounty only to an asker who asked after a delay the maker cannot pre-empt; or send the full forfeit to stakers and the burn and pay the asker a fixed fee on both verdicts so asking is not a prize the maker wants. At minimum the README should not claim the maker gets no bounty.","line":273,"path":"src/PinkyVault.sol","reproduction":"test/scratch/Judge.t.sol (passes on this tree, asserting the outcome): one PINKY staker; maker make(token, 1000e18, 1 hours, 10e18); close after the term; at endBlock + 32 wallet alt (not the maker) calls ask(id) -> promises(id).asker == alt; the Intake delivers a signed attestation with answer 1000e18 + 1 (Broken); payout(id). Expected per README: the maker's side receives 0, stakers 4.75 IMD, burn 4.75 IMD. Actual: alt receives 950000000000000000 wei, stakers 4275000000000000000, burn 4275000000000000000. Variant (test_MakerAskingFirstDeniesWatcherBounty): maker asks first; watcher's ask in the same block reverts TooEarly; the verdict lands with asker == maker; payout pays the watcher 0 and burns 4.75 IMD.","severity":"low","snippet":"        uint256 bounty = v.asker == v.maker ? 0 : amount * BOUNTY_BPS / 10_000;","title":"The 'maker earns no bounty' rule compares addresses only: a maker asks from a second wallet and keeps 10% of a forfeited bond, or asks first to deny a watcher's bounty for free"},{"citation":"resolved","description":"The earlier audit fix freezes lastUpdate while totalStaked == 0 so idle stream time is paid to the next staker instead of being stranded. The credit lands on the first update after a stake: rewardPerToken() (src/PinkyStaking.sol:67) adds (lastTimeRewardApplicable() - lastUpdate) * rewardRate * 1e18 / totalStaked over the whole idle gap against whatever the arriving staker put in. A bot that watches totalStaked fall to zero mid-stream stakes 1 wei of PINKY, claims and unstakes in one transaction and takes every idle second's reward; a staker who arrives one second later and stays seven days earns nothing of it. The contract's own docstring ('a staker who arrives in the block of a payout earns only for the time they stay') is the opposite of this behaviour at the totalStaked == 0 boundary, and the payout is independent of stake size and duration, so the idle reward is an MEV prize rather than a staking reward. Nothing is lost to the contract and the README states that idle time is paid to the next staker, so this is the sharp edge of a documented choice; reported by the math, flow and test-writer specialists, merged. Possible fixes (design decisions): pause the stream while nobody is staked (extend periodFinish by the idle span), or credit idle time pro rata to time actually staked.","line":53,"path":"src/PinkyStaking.sol","reproduction":"test/scratch/Judge.t.sol test_FlashStakeCollectsIdleStream (passes on this tree): staker stakes 1e18 PINKY; notify(7e18) (rate 1 IMD/day); warp 1 day; staker unstakes all (totalStaked == 0); warp 6 days (stream ended). flash, holding 1 wei PINKY, calls stake(1), claim(), unstake(1) in one transaction. Expected per the docstring: flash earns only for the time it stayed, i.e. ~0. Actual: flash receives 5999999999999961600 wei IMD (6 IMD less rate truncation). A staker who stakes 100e18 PINKY one second later and stays 7 days has earned() == 0.","severity":"low","snippet":"        if (totalStaked != 0) lastUpdate = lastTimeRewardApplicable();","title":"Stream time that passed with nobody staked is credited in full to the next staker at once, so a 1-wei flash stake in one transaction collects it"},{"citation":"resolved","description":"The vault asks with evidence 'chain'. OracleAttestation.sol lines 48-51 say agreed can be below quorum only for chain evidence, when the deployer's own rerun settled which of a split panel's answers was whole, i.e. the oracle still signs an answer it reproduced. The vault refuses it with InvalidAttestation, the callback reverts inside the Intake's try, the 0.5 IMD is spent and not retried, and after three attempts or REFUND_GRACE refund returns the bond to the maker whatever the signed answer said. The check is exactly what the oracle-consumer reference asks for ('Require agreed >= quorum'), so this is not a defect in the code but a trade-off the author should decide knowingly: accept agreed < quorum for this recipe because the rerun is the authority for chain facts, or keep the check and pick a launch quorum that a one- or two-member split still meets. Reported by the flow specialist; kept as information.","line":245,"path":"src/PinkyVault.sol","reproduction":"test/scratch/Judge.t.sol test_SplitPanelAnswerRefusedThenRefund (passes on this tree): make(token, 1000e18, 1 hours, 10e18); close; three times: ask(id) then deliver a correctly signed attestation with panelSize 7, quorum 5, agreed 4, answer 1000e18 + 1 (Broken) -> each delivery reverts InvalidAttestation; warp 1 day between attempts. refund(id) then succeeds: the maker ends with 98.5 IMD of their 100 (only the three fees lost) although the signed answer said Broken.","severity":"info","snippet":"                || a.panelSize < t.panelSize || a.quorum < t.quorum || a.quorum > a.panelSize || a.agreed < a.quorum","title":"A signed chain-evidence answer with agreed < quorum is refused, which after three attempts refunds a broken promise (design trade-off, follows the reference)"},{"citation":"resolved","description":"notify is permissionless and every call spreads the unpaid remainder over a fresh DURATION from now; MIN_REWARD (0.01 IMD) is the only cost. A griefer calling notify(0.01e18) once a day keeps the rate decaying by 6/7 per day, so after the seven days the README advertises stakers have received about 66% of a payout and the tail stretches on; a staker who leaves on day 7 forfeits the rest to later stakers. Nothing is lost and the griefer's IMD joins the stream. This is the known Synthetix notifyRewardAmount extension behaviour, accepted in the code comment with the floor as mitigation; reported by the economics specialist and the test writer, kept as information. If the seven-day promise should hold, restrict notify to the vault or only extend periodFinish when the added amount is at least a fraction of what is left.","line":106,"path":"src/PinkyStaking.sol","reproduction":"test/scratch/Judge.t.sol test_NotifyRestretch (passes on this tree): staker stakes 1000e18 PINKY; notify(22e18); then for seven days: warp +1 day, griefer notify(0.01e18). After day 7 staking.earned(staker) == 14545627271434941000 wei (about 14.5 IMD) against 22e18 expected from an untouched seven-day stream.","severity":"info","snippet":"            rewardRate = (amount + (periodFinish - block.timestamp) * rewardRate) / DURATION;","title":"Anyone can restretch the stakers' stream with a 0.01 IMD notify; daily calls cut what stakers receive in the advertised seven days by about a third"},{"citation":"resolved","description":"The vault pins window {fromBlock, toBlock} to the promise's exact block range. The README's live check covered 1,000 blocks (requests/oracle-test.body.json: 84167000 to 84168000) and says about 18,000 blocks have been attested; at roughly 10 blocks per second on Robinhood Chain a 30-day term is about 26 million blocks of Transfer logs for the panel to sum. If the oracle refuses or fails such a request (status 1 or 2, no callback), the promise can only be refunded after three paid attempts or REFUND_GRACE, so a maker who wants to dump without penalty picks the longest term. The oracle-consumer reference sets no block-range cap and allows relative windows up to 720 hours, so this may well be within its capacity; it cannot be reproduced against a mock and is recorded, merged from the flow and economics specialists, as a pre-launch check: run one live request over a window of at least a few days' blocks, and if the oracle has a limit, cap MAX_DURATION to it and document it. The same 'no answer favours the maker' path also covers a maker who floods their own token with self-transfers to make the scan impractical, which the README already names as a known limit.","line":93,"path":"src/PinkyVault.sol","reproduction":"Offline: make(token, maxOut, 30 days, 5e18) is accepted and bodyOf(id) builds a window of endBlock - startBlock blocks (about 25,920,000 at 10 blocks/s). Unverified: whether the live oracle answers a log-sum over that range inside ANSWER_TIMEOUT. The on-chain consequence of no callback (three asks, then refund to the maker) is covered by the project's own tests.","severity":"info","snippet":"    uint256 public constant MAX_DURATION = 30 days;","title":"Lead, not reproducible offline: a 30-day term produces a multi-million-block window that the oracle has only been shown to answer at about 1,000 to 18,000 blocks; an unanswered window refunds a broken"},{"citation":"resolved","description":"The burn share is a plain safeTransfer to the dEaD address. If the Robinhood Chain IMD token at 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127 refused that recipient (a blocklist or burn-address guard), payout for every broken promise would revert and the bond would be stuck in Broken with no other exit, since refund refuses verdicts. The RPC was unreachable from this machine, so the live token's code was not inspected; the mock IMD is a plain OpenZeppelin ERC-20. Reported by the test writer; kept as an owed pre-launch check: a fork run of the broken-promise payout against the live IMD.","line":288,"path":"src/PinkyVault.sol","reproduction":"On a fork of chain 4663: deploy the vault against the live IMD, make/close/ask, deliver a Broken verdict signed by a test signer set with setSigner, call payout. Expected: the transfer to dEaD succeeds. Not run here.","severity":"info","snippet":"        if (burned != 0) imd.safeTransfer(BURN, burned);","title":"Not reproducible offline: payout of a broken promise depends on the live IMD token accepting transfers to 0x...dEaD"},{"citation":"resolved","description":"The single immutable owner ($owner from launch policy) can replace the key every pending and future attestation is verified against, so they can sign any Kept or Broken verdict for any Asked promise (the chain id and block range are the only bindings and both are public), and can substitute the Intake that ask pays, collecting at most MAX_ATTEMPTS x the price recorded at make per promise. Combined with a second-wallet asker and a PINKY stake the owner can turn any open bond into 10% bounty plus a share of the 45% staker stream, with 45% burned. Mitigations present: the per-promise price cap from make, immutable IMD, staking and minBond, setters that cannot move bonds directly, and onlyOwner on all three setters. No unprivileged path reaches these powers. Documented in the README; recorded so the launch states who holds $owner and whether it is a multisig.","line":313,"path":"src/PinkyVault.sol","reproduction":"Precondition: the owner key. 1) owner calls setSigner(ownerKey). 2) for a promise in status Asked the owner signs an Attestation with chainId 4663, fromBlock/toBlock = startBlock/endBlock, panelSize 7, quorum 5, agreed 5, answerType 3, answer = abi.encode(maxOut + 1) in the vault's EIP-712 domain. 3) the asked Intake (or an owner Intake set with setProtocol before the ask) delivers it: status Broken although the maker kept the promise. 4) payout: 10% to the asker, 45% to stakers, 45% burned. Covered by the existing test suite's signer and intake mocks; this is the README's stated trust assumption.","severity":"info","snippet":"    function setSigner(address signer_) external onlyOwner nonReentrant {","title":"Trust assumption, not a defect: the vault owner can forge verdicts (setSigner) and route up to three oracle fees per promise through an Intake of their own (setProtocol)"}],"hash":"fb5e9787c34eeed9b4ca66dfabf344ca23ab0532c2b0193589c1d39a04353622","nodeId":"9c3ab10e-6d5f-4e17-b11d-b02528664863","outcome":"completed","summary":"Both embedded proofs fail on the current tree for the stated reasons, the cited snippets match the files exactly, and the tracked working tree is unchanged (only `.imd-findings.json` and `test/scratch/` were written, both ignored by git).\n\n## Result\n\n`.imd-findings.json` holds 9 findings and 18 coverage rows (all 16 entry points plus two invariants). Every specialist claim that can run offline was reproduced in `test/scratch/`; duplicates were merged.\n\n**Kept, by severity**\n\n1. **Medium.** `make` accepts a zero Intake quote that `ask` refuses forever, so the promise can only be refunded in full. Merged from four reports. Proof attached (the math specialist's, fails here with \"next call did not revert as expected\").\n2. **Medium.** An Intake price rise during a term strands every open promise behind the `maxPrice` cap, so a broken maker is refunded in full. Documented in the README as a trade-off, but it voids the core guarantee for the whole open book. Proof attached (the economics specialist's, fails here). The suggested fix keeps the owner-redirect protection by keying the cap to the intake and action recorded at `make`.\n3. **Low.** The \"maker earns no bounty\" rule is address-keyed. A second wallet collects 10% of the forfeited bond, and a maker who asks first denies a watcher's bounty for free. Three reports merged.\n4. **Low.** Idle stream time is paid in full to the next staker at once, so a 1-wei flash stake collects it in one transaction. Three reports merged.\n5. **Info.** Refusing `agreed < quorum` on chain evidence refunds a broken maker after three attempts. The code follows the reference, so it is recorded as a trade-off, not a defect.\n6. **Info.** Permissionless `notify` restretch for 0.01 IMD per call.\n7. **Info.** 30-day windows are unverified against the live oracle. Not reproducible offline.\n8. **Info.** Burn to the dEaD address depends on the live IMD token. Not reproducible offline.\n9. **Info.** Owner trust assumptions (setSigner, setProtocol).\n\n**Dropped.** Stale `promiseIdFor` entries for earlier asks (no effect, every path checks status first), the slither reentrancy lines (every entry is `nonReentrant` and the Intake is owner-set), and the aderyn style items.\n\n**My own pass found nothing new.** The attestation library is byte-identical to the reference and the conformance vector passes. Constructors make no external calls, arguments are in declaration order with supported types, and runtime and init sizes sit well under the deployment limits.\n\n**Unverified.** The Robinhood RPC was unreachable from this machine, so the specialists' claim that the live Intake returns 0 for an unsold action, and the live IMD token's behaviour on burns, were not independently checked. Both are flagged in the findings.","treeHash":null,"usage":{"cachedInputTokens":1273825,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":37306,"runtime":"claude","turns":26,"wallClockMs":652640}}],"verification":[{"checks":[{"durationMs":40598,"exitCode":0,"name":"build","output":"Compiling 62 files with Solc 0.8.26\nSolc 0.8.26 finished in 40.46s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:65:16\n   │\n65 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:168:13\n    │\n168 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:176:24\n    │\n176 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:177:21\n    │\n177 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:189:13\n    │\n189 │         if (block.timestamp < v.endTime) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:191:22\n    │\n191 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:192:22\n    │\n192 │         v.closedAt = 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/PinkyStaking.sol:107:28\n    │\n107 │         uint256 leftover = block.timestamp < periodFinish ? (periodFinish - block.timestamp) * rewardRate : 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/PinkyStaking.sol:112:13\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\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/PinkyStaking.sol:112:30\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\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/PinkyVault.sol:203:17\n    │\n203 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:247:9\n    │\n247 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:224:21\n    │\n224 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:242:13\n    │\n242 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:273:9\n    │\n273 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:307:9\n    │\n307 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:314:17\n    │\n314 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:319:42\n    │\n319 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:327:79\n    │\n327 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:331:60\n    │\n331 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:335:102\n    │\n335 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":3665,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 497.02µs (364.67µs CPU time)\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 548.01µs (281.34µs CPU time)\n\nRan 11 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 510279, ~: 510186)\nLogs:\n  Bound result 999990000000000022214\n  Bound result 999674\n  Bound result 8000000000000000000\n  Bound result 3\n\n[PASS] test_ADustNotifyDoesNotSlowTheStream() (gas: 1124098)\n[PASS] test_AFlashStakeDuringAPauseEarnsNothing() (gas: 941110)\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 667439)\n[PASS] test_ALargerNotifyStillRaisesTheRateOverSevenDays() (gas: 390492)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 817711)\n[PASS] test_AStreamThatEndedWhilePausedResumesInFull() (gas: 763565)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6599)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77753)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 798034)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 645979)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 144.20ms (146.94ms CPU time)\n\nRan 4 tests for test/OracleConsumerVectorKey.t.sol:OracleConsumerVectorKeyTest\n[PASS] test_AnAnswerSignedByTheVectorKeyIsAcceptedThroughTheCallback() (gas: 1501262)\n[PASS] test_TheSameAnswerIsNotAcceptedTwice() (gas: 1551494)\n[PASS] test_TheVaultAtTheVectorAddressReproducesTheDigest() (gas: 21793)\n[PASS] test_TheVectorSignatureGetsPastTheSignatureCheckInTheCallback() (gas: 1361954)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 144.31ms (2.28ms CPU time)\n\nRan 55 tests for test/PinkyVaultEdges.t.sol:PinkyVaultEdgesTest\n[PASS] testFuzz_BrokenSplitIsExact(uint256,uint8,bool,bool) (runs: 1000, μ: 2493580, ~: 2431084)\nLogs:\n  Bound result 97856735841622468032\n  Bound result 1\n\n[PASS] testFuzz_KeptExactlyWhenTheSumIsWithinThePromise(uint256,uint256) (runs: 1000, μ: 1511676, ~: 1512208)\n[PASS] testFuzz_RefundReturnsExactlyTheUnspentBond(uint256,uint8) (runs: 500, μ: 1634197, ~: 1415304)\nLogs:\n  Bound result 25324257525775008602\n  Bound result 3\n\n[PASS] test_AMakerAskerWithStakersSplitsTheWholeRemainderBetweenStakersAndBurn() (gas: 1917860)\n[PASS] test_ANewSignerAppliesToPendingRequests() (gas: 1570816)\n[PASS] test_APriceRiseToTheWholeBondLeavesAKeptMakerNothing() (gas: 1583594)\n[PASS] test_APromiseOfZeroIsBrokenByOneWei() (gas: 1492793)\n[PASS] test_AQuoteThatDropsToZeroAfterMakeLeavesOnlyTheRefund() (gas: 1563432)\n[PASS] test_ARefusedCallbackUnderTheStipendLeavesTheRequestPending() (gas: 1709737)\n[PASS] test_ASecondAskAfterAPriceRiseIsHeldToWhatIsLeft() (gas: 2429214)\n[PASS] test_ASwitchAwayAndBackLeavesThePromiseAtItsOwnProtocol() (gas: 3519503)\n[PASS] test_AZeroAnswerIsStoredNotRefused() (gas: 1494959)\n[PASS] test_AnAnswerToARefundedRequestIsRefused() (gas: 2401631)\n[PASS] test_AnyoneMayCloseIncludingTheMaker() (gas: 378300)\n[PASS] test_AnyoneMayRefundButOnlyTheMakerIsPaid() (gas: 463649)\n[PASS] test_AskEmitsAsked() (gas: 1282944)\n[PASS] test_AskExactlyAtTheSettleDelay() (gas: 1413224)\n[PASS] test_AskLeavesNothingBehindWhenTheIntakeReverts() (gas: 870741)\n[PASS] test_AskRefusesARequestIdTheIntakeAlreadyGaveOut() (gas: 1448489)\n[PASS] test_AskRefusesAnIntakeThatPullsTheWrongAmount() (gas: 1349702)\n[PASS] test_AskRefusesSettledPromises() (gas: 2031715)\n[PASS] test_AskUsesTheArbitrumBlockForTheSettleDelay() (gas: 1384562)\n[PASS] test_AskWritesTheCurrentPanelAndValidityIntoTheBody() (gas: 1424479)\n[PASS] test_BodyOfRefusesUnknownIds() (gas: 24380)\n[PASS] test_BodyQuotesTheMakerAndTokenInLowercaseHex() (gas: 355016)\n[PASS] test_CloseExactlyAtTheDeadline() (gas: 417270)\n[PASS] test_CloseRefusesUnknownIds() (gas: 350862)\n[PASS] test_ConstructorPanelBoundaries() (gas: 70084)\n[PASS] test_ConstructorRecordsTheLaunchArguments() (gas: 107345)\n[PASS] test_ConstructorRefusesEveryZero() (gas: 13736)\n[PASS] test_KeptAfterThreeAsksReturnsWhatIsLeft() (gas: 3194190)\n[PASS] test_MakeAcceptsTheExactBoundaries() (gas: 607272)\n[PASS] test_MakeEmitsMade() (gas: 297731)\n[PASS] test_MakeNeedsAnAllowanceAndABalance() (gas: 225053)\n[PASS] test_MakeNeedsThreeOracleFeesEvenAboveTheMinimumBond() (gas: 381950)\n[PASS] test_MakeRefusesAnImdThatTakesAFee() (gas: 812981)\n[PASS] test_MakeRefusesTheVaultAndTheStakingAsTheToken() (gas: 503687)\n[PASS] test_OwnerSettersRefuseBadValues() (gas: 330523)\n[PASS] test_PayoutOfAFullySpentBondMovesNothing() (gas: 6396345)\n[PASS] test_PayoutRefusesTheOtherStates() (gas: 1526910)\n[PASS] test_PayoutWhenTheStakerShareIsBelowTheStreamFloorBurnsIt() (gas: 3547656)\n[PASS] test_RefundAfterOneUnansweredAskWaitsForTheGrace() (gas: 1541978)\n[PASS] test_RefundDoesNotNeedTheIntake() (gas: 1596199)\n[PASS] test_RefundRefusesSettledPromises() (gas: 1580004)\n[PASS] test_RefusesACallbackFromAnIntakeThatWasNotAsked() (gas: 2266804)\n[PASS] test_RefusesASignatureForAnotherChain() (gas: 1403847)\n[PASS] test_RefusesASignatureMadeForAnotherVault() (gas: 1430317)\n[PASS] test_RefusesATamperedOrMalformedSignature() (gas: 1596447)\n[PASS] test_RefusesAnAnswerOfTheWrongLength() (gas: 1657797)\n[PASS] test_RefusesAnAttestationIssuedTooFarAhead() (gas: 1528141)\n[PASS] test_RefusesAnExpiredAttestation() (gas: 1528446)\n[PASS] test_RefusesAnInconsistentPanel() (gas: 1684319)\n[PASS] test_RefusesTheSameOracleRequestIdForTwoPromises() (gas: 2790736)\n[PASS] test_TheCallbackSucceedsUnderTheRealStipendFromCold() (gas: 2995976)\n[PASS] test_TheOwnerCannotMoveABond() (gas: 1498742)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 144.34ms (398.95ms CPU time)\n\nRan 34 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1659554, ~: 1544864)\nLogs:\n  Bound result 95000000000000002404\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1468179)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1512874)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1447096)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2350842)\n[PASS] test_APriceAboveWhatIsLeftOfTheBondIsStillRefused() (gas: 1384587)\n[PASS] test_APriceRiseAtTheProtocolThePromiseWasMadeUnderIsPaidFromTheBond() (gas: 1637372)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2339788)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2350515)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1472730)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2421697)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1349142)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnderAtAnotherProtocol() (gas: 2275703)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 424318)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1402944)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2064705)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1576797)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1432419)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1551979)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 448491)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1545130)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 336090)\n[PASS] test_MakeRefusesAPromiseWhileTheIntakeQuotesZero() (gas: 619021)\n[PASS] test_MakeRefusesBadInput() (gas: 259241)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 318712)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1327100)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223155)\n[PASS] test_PayoutNeedsAVerdict() (gas: 356765)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 478393)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3132675)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1672772)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1348230)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1355304)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1519902)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 144.41ms (163.60ms CPU time)\n\nRan 24 tests for test/PinkyStakingEdges.t.sol:PinkyStakingEdgesTest\n[PASS] testFuzz_ANotifyNeverSlowsOrShortensTheStream(uint256,uint256,uint256) (runs: 1000, μ: 427988, ~: 429418)\nLogs:\n  Bound result 999990000000000002090\n  Bound result 999990000000000000601\n  Bound result 171797\n\n[PASS] testFuzz_APauseOfAnyLengthCreditsNothingToTheStakerWhoResumes(uint256,uint256,uint256) (runs: 500, μ: 667642, ~: 668739)\nLogs:\n  Bound result 6000000000000000000\n  Bound result 19398\n  Bound result 3520000\n\n[PASS] testFuzz_NoSequenceOfNotifiesPaysOutMoreThanWasPutIn(uint256,uint256,uint256,uint256) (runs: 500, μ: 749483, ~: 749526)\nLogs:\n  Bound result 999990000000000000321\n  Bound result 997043556547795733515\n  Bound result 1067979\n  Bound result 12186193376243190993\n\n[PASS] testFuzz_StreamIsSharedProRataAtEveryMoment(uint256,uint256,uint256,uint256) (runs: 1000, μ: 425551, ~: 425568)\nLogs:\n  Bound result 499999999000000000000000201\n  Bound result 99000000000000021818\n  Bound result 250624308548223347434731\n  Bound result 5699\n\n[PASS] test_AFlashStakeInTheSameBlockEarnsNothing() (gas: 591799)\n[PASS] test_AFloorNotifyAtTheTailOfAFastStreamIsPaidInSeconds() (gas: 447718)\n[PASS] test_ANotifyThatExactlyMatchesTheRateRestretchesToSevenDays() (gas: 408062)\n[PASS] test_APartialUnstakeDoesNotPauseTheStream() (gas: 603824)\n[PASS] test_ASmallNotifyMidStreamKeepsTheRateAndEndsSooner() (gas: 449364)\n[PASS] test_AStakerWhoArrivesLateEarnsOnlyFromThen() (gas: 470414)\n[PASS] test_AStreamThatEndedWithStakersDoesNotResume() (gas: 596276)\n[PASS] test_ClaimPaysExactlyWhatWasEarnedAndNoMore() (gas: 697870)\n[PASS] test_NobodyUnstakesMoreThanTheyStaked() (gas: 403530)\n[PASS] test_NotifyIsRefusedWhileNobodyIsStaked() (gas: 227640)\n[PASS] test_NotifyNeedsAnAllowanceAndAcceptsExactlyTheFloor() (gas: 437201)\n[PASS] test_OneWeiStakeBesideTheWholeSupplyRoundsToZeroNotUp() (gas: 508683)\n[PASS] test_OneWeiUnderTheBoundaryKeepsTheRateAndEndsASecondEarly() (gas: 392323)\n[PASS] test_PinkyNeverLeavesExceptByUnstake() (gas: 454821)\n[PASS] test_RewardsSurviveAFullUnstake() (gas: 530923)\n[PASS] test_StakeNeedsAnAllowance() (gas: 162427)\n[PASS] test_StreamIsLinearInTime() (gas: 487496)\n[PASS] test_TheStreamPausesWhileNobodyIsStakedAndResumesForTheNextStaker() (gas: 732337)\n[PASS] test_TheWholeSupplyCanBeStakedWithoutOverflow() (gas: 493666)\n[PASS] test_ZeroAmountsAreRefused() (gas: 129218)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 144.45ms (559.08ms CPU time)\n\nRan 1 test for test/PinkyStakingInvariant.t.sol:PinkyStakingInvariantTest\n[PASS]\nPinkyStakingInvariantTest invariants:\n[PASS] invariant_APausedStreamIsFrozen\n[PASS] invariant_ClockIsMonotonic\n[PASS] invariant_ImdHeldCoversWhatIsOwed\n[PASS] invariant_NobodyWithdrawsMoreThanTheyStaked\n[PASS] invariant_NothingIsPaidTwice\n[PASS] invariant_PinkyHeldEqualsTheStakes\n[PASS] invariant_TheStreamAccountsForEverything\n PinkyStakingInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭----------------+----------+-------+---------+----------╮\n| Contract       | Selector | Calls | Reverts | Discards |\n+========================================================+\n| StakingHandler | claim    | 3302  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | notify   | 3277  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | pass     | 3303  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | stake    | 3242  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | unstake  | 3260  | 0       | 0        |\n╰----------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 131078288184399784491\n  Bound result 999990000000000004251\n  Bound result 999990000000000000367\n  Bound result 999990000000000000537\n  Bound result 6848\n  Bound result 4088\n  Bound result 999990023148148148149\n  Bound result 864000\n  Bound result 6847\n  Bound result 4000\n  Bound result 10085\n  Bound result 10000000000000000\n  Bound result 16384\n  Bound result 59917125098999462555\n  Bound result 608000\n  Bound result 604800\n  Bound result 999990000000000000255\n  Bound result 4542\n  Bound result 999990000000000003601\n  Bound result 6000000000000000000\n  Bound result 999999999999999999998\n  Bound result 999990000000000000724\n  Bound result 592069\n  Bound result 7200\n  Bound result 3\n  Bound result 8827\n  Bound result 10000000000000001\n  Bound result 256182932586104720382\n  Bound result 999990000000000000401\n  Bound result 200\n  Bound result 18\n  Bound result 3920\n  Bound result 299555\n  Bound result 3134\n  Bound result 999990000000000001001\n  Bound result 5921\n  Bound result 3\n  Bound result 999990000000084010032\n  Bound result 100000000000000000\n  Bound result 743620\n  Bound result 10023148148153555\n  Bound result 396801\n  Bound result 999990000000005184001\n  Bound result 840204000848099473148\n  Bound result 8149\n  Bound result 710618455191144070303\n  Bound result 12345\n  Bound result 999990000000001000001\n  Bound result 14400\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.50s (3.50s CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_BondIsTheDepositLessTheFeesPaid\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n[PASS] invariant_VerdictsAreFinal\n PinkyInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| PinkyHandler | answer         | 1491  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | ask            | 1498  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | askAsMaker     | 1518  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | close          | 1447  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | make           | 1498  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | pass           | 1408  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | payout         | 1514  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | refund         | 1513  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | setPrice       | 1485  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | switchProtocol | 1515  | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| PinkyHandler | toggleStake    | 1497  | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2989\n  Bound result 3863676351\n  Bound result 27\n  Bound result 241\n  Bound result 38399\n  Bound result 16\n  Bound result 2170272752161702582\n  Bound result 2000000000000000\n  Bound result 101\n  Bound result 3600\n  Bound result 200186496412096732855\n  Bound result 10364\n  Bound result 98434\n  Bound result 100000\n  Bound result 35884083900570475500\n  Bound result 2132988\n  Bound result 1\n  Bound result 1\n  Bound result 43489181743514892417\n  Bound result 18942\n  Bound result 2\n  Bound result 2\n  Bound result 7786284671014123893\n  Bound result 2396945\n  Bound result 3\n  Bound result 2\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 2\n  Bound result 748\n  Bound result 5234\n  Bound result 181\n  Bound result 3\n  Bound result 32398686287248197068\n  Bound result 1875\n  Bound result 2\n  Bound result 2827\n  Bound result 12345\n  Bound result 3\n  Bound result 1\n  Bound result 1\n  Bound result 3227523536055406033\n  Bound result 15246\n  Bound result 32060\n  Bound result 3790948133\n  Bound result 4979810593104229434\n  Bound result 2717\n  Bound result 8971\n  Bound result 3\n  Bound result 3\n  Bound result 3\n  Bound result 5332\n  Bound result 2\n  Bound result 2\n  Bound result 3\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.58s (3.58s CPU time)\n\nRan 9 test suites in 3.58s (7.80s CPU time): 137 tests passed, 0 failed, 0 skipped (137 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":100,\"README.md\":135,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":127,\"src/PinkyVault.sol\":403,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/OracleConsumerVectorKey.t.sol\":170,\"test/PinkyInvariant.t.sol\":380,\"test/PinkyStaking.t.sol\":229,\"test/PinkyStakingEdges.t.sol\":447,\"test/PinkyStakingInvariant.t.sol\":195,\"test/PinkyVault.t.sol\":689,\"test/PinkyVaultEdges.t.sol\":1076,\"test/mocks/Mocks.sol\":75,\"test/mocks/MoreMocks.sol\":137,\"test/utils/PinkyFixture.sol\":155,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"23c319f03b28628c2e9bb057f89b5f79cbede1891cc2dd8d0074cc84ffcc0797","verifiedTreeHash":"fb6dce923ffe31ee3d047f1a1bbaeb6588d63809","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":14824,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 14.70s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:62:16\n   │\n62 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:159:13\n    │\n159 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:167:24\n    │\n167 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:168:21\n    │\n168 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:177:13\n    │\n177 │         if (block.timestamp < v.endTime) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:179:22\n    │\n179 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:180:22\n    │\n180 │         v.closedAt = 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/PinkyStaking.sol:103:13\n    │\n103 │         if (block.timestamp >= periodFinish) {\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/PinkyVault.sol:191:17\n    │\n191 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:229:9\n    │\n229 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:206:21\n    │\n206 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:224:13\n    │\n224 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:255:9\n    │\n255 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:289:9\n    │\n289 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:296:17\n    │\n296 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:301:42\n    │\n301 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:309:79\n    │\n309 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:313:60\n    │\n313 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:317:102\n    │\n317 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":1453,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 553.96µs (280.48µs CPU time)\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.31ms (503.52µs CPU time)\n\nRan 7 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 494847, ~: 494925)\nLogs:\n  Bound result 998605831694082831980\n  Bound result 67\n  Bound result 99999999999999999998\n  Bound result 43231626610605579007\n\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 597821)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 807662)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6511)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77665)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 792670)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 630762)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 12.97ms (13.63ms CPU time)\n\nRan 31 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1616490, ~: 1517454)\nLogs:\n  Bound result 26084725840193879204\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1440889)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1485584)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1419806)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2323480)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2310174)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2323141)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1445462)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2394342)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1321846)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnder() (gas: 2235970)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 379972)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1375654)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2032584)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1549545)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1405129)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1522362)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 404173)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1517818)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 291750)\n[PASS] test_MakeRefusesBadInput() (gas: 259179)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 274394)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1299810)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223111)\n[PASS] test_PayoutNeedsAVerdict() (gas: 312403)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 434049)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3105236)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1645504)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1320962)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1327992)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1492634)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 35.45ms (81.26ms CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n PinkyInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| PinkyHandler | answer      | 1050  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | ask         | 1043  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | close       | 1064  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | make        | 980   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | pass        | 1002  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | payout      | 1003  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | refund      | 1026  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | toggleStake | 1024  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 19200\n  Bound result 5006\n  Bound result 160740\n  Bound result 2750\n  Bound result 196679\n  Bound result 92000\n  Bound result 7000000000000000000\n  Bound result 290058\n  Bound result 1\n  Bound result 1\n  Bound result 45000000000000000010\n  Bound result 604800\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 1\n  Bound result 1960\n  Bound result 45000000000000006460\n  Bound result 2106546\n  Bound result 3\n  Bound result 3\n  Bound result 2\n  Bound result 1\n  Bound result 3\n  Bound result 3\n  Bound result 45000000000000001109\n  Bound result 2592000\n  Bound result 3\n  Bound result 50000000000000000000\n  Bound result 3479\n  Bound result 45000000000000000905\n  Bound result 921\n  Bound result 45000000000000010001\n  Bound result 373031\n  Bound result 2\n  Bound result 1\n  Bound result 234\n  Bound result 73042\n  Bound result 5\n  Bound result 3\n  Bound result 418\n  Bound result 3\n  Bound result 4\n  Bound result 1\n  Bound result 1\n  Bound result 6\n  Bound result 4\n  Bound result 45000000000000007068\n  Bound result 200000\n  Bound result 121600\n  Bound result 200\n  Bound result 6\n  Bound result 5\n  Bound result 5\n  Bound result 3\n  Bound result 8\n  Bound result 4\n  Bound result 3\n  Bound result 8\n  Bound result 6\n  Bound result 46000000000000000001\n  Bound result 3600\n  Bound result 45000000000000011374\n  Bound result 2339\n  Bound result 3\n  Bound result 5\n  Bound result 10\n  Bound result 5\n  Bound result 13876\n  Bound result 100000\n  Bound result 10\n  Bound result 5\n  Bound result 5\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.36s (1.36s CPU time)\n\nRan 5 test suites in 1.37s (1.41s CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":63,\"README.md\":115,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":112,\"src/PinkyVault.sol\":385,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/PinkyInvariant.t.sol\":219,\"test/PinkyStaking.t.sol\":146,\"test/PinkyVault.t.sol\":625,\"test/mocks/Mocks.sol\":75,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3786,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/PinkyVault.sol:188: Reentrancy in PinkyVault.ask(uint256) (src/PinkyVault.sol#188-230):\n[medium/high] incorrect-equality at src/PinkyStaking.sol:90: PinkyStaking.claim() (src/PinkyStaking.sol#90-96) uses a dangerous strict equality:\n[low/medium] reentrancy-benign at src/PinkyVault.sol:188: Reentrancy in PinkyVault.ask(uint256) (src/PinkyVault.sol#188-230):\n[low/medium] timestamp at src/PinkyStaking.sol:61: PinkyStaking.lastTimeRewardApplicable() (src/PinkyStaking.sol#61-63) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyVault.sol:188: PinkyVault.ask(uint256) (src/PinkyVault.sol#188-230) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyVault.sol:174: PinkyVault.close(uint256) (src/PinkyVault.sol#174-182) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:99: PinkyStaking.notify(uint256) (src/PinkyStaking.sol#99-111) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyVault.sol:293: PinkyVault.refund(uint256) (src/PinkyVault.sol#293-307) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:90: PinkyStaking.claim() (src/PinkyStaking.sol#90-96) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":550,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PinkyVault.sol:154: Reentrancy: State change after external call (7 places)\n[low] centralization-risk at src/PinkyVault.sol:309: Centralization Risk (3 places)\n[low] internal-function-used-once at src/OracleAttestation.sol:200: Internal Function Used Only Once\n[low] large-numeric-literal at src/LaunchToken.sol:9: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/PinkyStaking.sol:67: Literal Instead of Constant (4 places)\n[low] local-variable-shadowing at src/PinkyVault.sol:47: Local Variable Shadows State Variable (3 places)\n[low] non-reentrant-not-first at src/PinkyVault.sol:309: `nonReentrant` is Not the First Modifier (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3ae08bf7d917dab195d713506f68627ce050b6425055a68fd4b0d07e135160c2","verifiedTreeHash":"df46c6f97e697cfd25618465b044ca7f90cc1b87","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":34265,"exitCode":0,"name":"build","output":"Compiling 62 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.13s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:62:16\n   │\n62 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:159:13\n    │\n159 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:167:24\n    │\n167 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:168:21\n    │\n168 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:177:13\n    │\n177 │         if (block.timestamp < v.endTime) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:179:22\n    │\n179 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:180:22\n    │\n180 │         v.closedAt = 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/PinkyStaking.sol:103:13\n    │\n103 │         if (block.timestamp >= periodFinish) {\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/PinkyVault.sol:191:17\n    │\n191 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:229:9\n    │\n229 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:206:21\n    │\n206 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:224:13\n    │\n224 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:255:9\n    │\n255 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:289:9\n    │\n289 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:296:17\n    │\n296 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:301:42\n    │\n301 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:309:79\n    │\n309 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:313:60\n    │\n313 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:317:102\n    │\n317 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":5828,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 702.54µs (710.07µs CPU time)\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 780.62µs (264.17µs CPU time)\n\nRan 7 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 494837, ~: 494880)\nLogs:\n  Bound result 448213719029809367064\n  Bound result 260653\n  Bound result 14156314744126836889\n  Bound result 75656824874619839991\n\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 597821)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 807662)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6511)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77665)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 792670)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 630762)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 77.35ms (78.44ms CPU time)\n\nRan 4 tests for test/OracleConsumerVectorKey.t.sol:OracleConsumerVectorKeyTest\n[PASS] test_AnAnswerSignedByTheVectorKeyIsAcceptedThroughTheCallback() (gas: 1473994)\n[PASS] test_TheSameAnswerIsNotAcceptedTwice() (gas: 1524226)\n[PASS] test_TheVaultAtTheVectorAddressReproducesTheDigest() (gas: 21793)\n[PASS] test_TheVectorSignatureGetsPastTheSignatureCheckInTheCallback() (gas: 1334686)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 109.56ms (2.15ms CPU time)\n\nRan 31 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1618764, ~: 1517341)\nLogs:\n  Bound result 95000000000000259201\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1440889)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1485584)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1419806)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2323480)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2310174)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2323141)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1445462)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2394342)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1321846)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnder() (gas: 2235970)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 379972)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1375654)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2032584)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1549545)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1405129)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1522362)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 404173)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1517818)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 291750)\n[PASS] test_MakeRefusesBadInput() (gas: 259179)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 274394)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1299810)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223111)\n[PASS] test_PayoutNeedsAVerdict() (gas: 312403)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 434049)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3105236)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1645504)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1320962)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1327992)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1492634)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 109.74ms (131.33ms CPU time)\n\nRan 17 tests for test/PinkyStakingEdges.t.sol:PinkyStakingEdgesTest\n[PASS] testFuzz_NoSequenceOfNotifiesPaysOutMoreThanWasPutIn(uint256,uint256,uint256,uint256) (runs: 500, μ: 743858, ~: 744132)\nLogs:\n  Bound result 326991902309260380868\n  Bound result 110959761699229902\n  Bound result 592281\n  Bound result 48257252049426099534\n\n[PASS] testFuzz_StreamIsSharedProRataAtEveryMoment(uint256,uint256,uint256,uint256) (runs: 1000, μ: 420475, ~: 420534)\nLogs:\n  Bound result 31045840597852206031663232\n  Bound result 62576089425314881022\n  Bound result 10000000000000001\n  Bound result 578223\n\n[PASS] test_AFlashStakeInTheSameBlockEarnsNothing() (gas: 586529)\n[PASS] test_ANotifyMidStreamRestretchesWhatIsLeft() (gas: 401314)\n[PASS] test_AStakerWhoArrivesLateEarnsOnlyFromThen() (gas: 465240)\n[PASS] test_ClaimPaysExactlyWhatWasEarnedAndNoMore() (gas: 692598)\n[PASS] test_NobodyUnstakesMoreThanTheyStaked() (gas: 398533)\n[PASS] test_NotifyIsRefusedWhileNobodyIsStaked() (gas: 223034)\n[PASS] test_NotifyNeedsAnAllowanceAndAcceptsExactlyTheFloor() (gas: 432416)\n[PASS] test_OneWeiStakeBesideTheWholeSupplyRoundsToZeroNotUp() (gas: 503596)\n[PASS] test_PinkyNeverLeavesExceptByUnstake() (gas: 449992)\n[PASS] test_RewardsSurviveAFullUnstake() (gas: 526017)\n[PASS] test_StakeNeedsAnAllowance() (gas: 157939)\n[PASS] test_StreamIsLinearInTime() (gas: 482366)\n[PASS] test_TheIdleStreamGoesToTheNextStakerAtOnce() (gas: 774069)\n[PASS] test_TheWholeSupplyCanBeStakedWithoutOverflow() (gas: 488611)\n[PASS] test_ZeroAmountsAreRefused() (gas: 129218)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 124.21ms (236.98ms CPU time)\n\nRan 52 tests for test/PinkyVaultEdges.t.sol:PinkyVaultEdgesTest\n[PASS] testFuzz_BrokenSplitIsExact(uint256,uint8,bool,bool) (runs: 1000, μ: 2436883, ~: 2403732)\nLogs:\n  Bound result 51306743112061432561\n  Bound result 2\n\n[PASS] testFuzz_KeptExactlyWhenTheSumIsWithinThePromise(uint256,uint256) (runs: 1000, μ: 1484660, ~: 1484952)\n[PASS] testFuzz_RefundReturnsExactlyTheUnspentBond(uint256,uint8) (runs: 500, μ: 1630963, ~: 1388026)\nLogs:\n  Bound result 58644197202801986036\n  Bound result 2\n\n[PASS] test_AMakerAskerWithStakersSplitsTheWholeRemainderBetweenStakersAndBurn() (gas: 1885825)\n[PASS] test_ANewSignerAppliesToPendingRequests() (gas: 1543504)\n[PASS] test_APromiseOfZeroIsBrokenByOneWei() (gas: 1465503)\n[PASS] test_ARefusedCallbackUnderTheStipendLeavesTheRequestPending() (gas: 1682425)\n[PASS] test_AZeroAnswerIsStoredNotRefused() (gas: 1467691)\n[PASS] test_AnAnswerToARefundedRequestIsRefused() (gas: 2374235)\n[PASS] test_AnyoneMayCloseIncludingTheMaker() (gas: 333938)\n[PASS] test_AnyoneMayRefundButOnlyTheMakerIsPaid() (gas: 419259)\n[PASS] test_AskEmitsAsked() (gas: 1255632)\n[PASS] test_AskExactlyAtTheSettleDelay() (gas: 1385931)\n[PASS] test_AskLeavesNothingBehindWhenTheIntakeReverts() (gas: 843411)\n[PASS] test_AskRefusesARequestIdTheIntakeAlreadyGaveOut() (gas: 1393895)\n[PASS] test_AskRefusesAnIntakeThatPullsTheWrongAmount() (gas: 1339470)\n[PASS] test_AskRefusesSettledPromises() (gas: 1960098)\n[PASS] test_AskUsesTheArbitrumBlockForTheSettleDelay() (gas: 1357269)\n[PASS] test_AskWritesTheCurrentPanelAndValidityIntoTheBody() (gas: 1394935)\n[PASS] test_BodyOfRefusesUnknownIds() (gas: 24358)\n[PASS] test_BodyQuotesTheMakerAndTokenInLowercaseHex() (gas: 310676)\n[PASS] test_CloseExactlyAtTheDeadline() (gas: 372886)\n[PASS] test_CloseRefusesUnknownIds() (gas: 306522)\n[PASS] test_ConstructorPanelBoundaries() (gas: 70062)\n[PASS] test_ConstructorRecordsTheLaunchArguments() (gas: 107323)\n[PASS] test_ConstructorRefusesEveryZero() (gas: 13714)\n[PASS] test_KeptAfterThreeAsksReturnsWhatIsLeft() (gas: 3166744)\n[PASS] test_MakeAcceptsTheExactBoundaries() (gas: 518614)\n[PASS] test_MakeEmitsMade() (gas: 253391)\n[PASS] test_MakeNeedsAnAllowanceAndABalance() (gas: 224925)\n[PASS] test_MakeNeedsThreeOracleFeesEvenAboveTheMinimumBond() (gas: 332009)\n[PASS] test_MakeRefusesAnImdThatTakesAFee() (gas: 812928)\n[PASS] test_MakeRefusesTheVaultAndTheStakingAsTheToken() (gas: 415029)\n[PASS] test_MakeUnderAZeroQuoteCanNeverBeAskedOnlyRefunded() (gas: 586221)\n[PASS] test_OwnerSettersRefuseBadValues() (gas: 330479)\n[PASS] test_PayoutOfAFullySpentBondMovesNothing() (gas: 6337051)\n[PASS] test_PayoutRefusesTheOtherStates() (gas: 1499620)\n[PASS] test_PayoutWhenTheStakerShareIsBelowTheStreamFloorBurnsIt() (gas: 3515729)\n[PASS] test_RefundAfterOneUnansweredAskWaitsForTheGrace() (gas: 1514688)\n[PASS] test_RefundDoesNotNeedTheIntake() (gas: 1568885)\n[PASS] test_RefundRefusesSettledPromises() (gas: 1552736)\n[PASS] test_RefusesACallbackFromAnIntakeThatWasNotAsked() (gas: 2239514)\n[PASS] test_RefusesASignatureForAnotherChain() (gas: 1376535)\n[PASS] test_RefusesASignatureMadeForAnotherVault() (gas: 1403005)\n[PASS] test_RefusesATamperedOrMalformedSignature() (gas: 1569135)\n[PASS] test_RefusesAnAnswerOfTheWrongLength() (gas: 1630507)\n[PASS] test_RefusesAnAttestationIssuedTooFarAhead() (gas: 1500851)\n[PASS] test_RefusesAnExpiredAttestation() (gas: 1501134)\n[PASS] test_RefusesAnInconsistentPanel() (gas: 1656985)\n[PASS] test_RefusesTheSameOracleRequestIdForTwoPromises() (gas: 2736156)\n[PASS] test_TheCallbackSucceedsUnderTheRealStipendFromCold() (gas: 2941418)\n[PASS] test_TheOwnerCannotMoveABond() (gas: 1471453)\nSuite result: ok. 52 passed; 0 failed; 0 skipped; finished in 124.25ms (366.86ms CPU time)\n\nRan 1 test for test/PinkyStakingInvariant.t.sol:PinkyStakingInvariantTest\n[PASS]\nPinkyStakingInvariantTest invariants:\n[PASS] invariant_ClockIsMonotonic\n[PASS] invariant_ImdHeldCoversWhatIsOwed\n[PASS] invariant_NobodyWithdrawsMoreThanTheyStaked\n[PASS] invariant_NothingIsPaidTwice\n[PASS] invariant_PinkyHeldEqualsTheStakes\n[PASS] invariant_TheStreamAccountsForEverything\n PinkyStakingInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭----------------+----------+-------+---------+----------╮\n| Contract       | Selector | Calls | Reverts | Discards |\n+========================================================+\n| StakingHandler | claim    | 3274  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | notify   | 3297  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | pass     | 3315  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | stake    | 3290  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | unstake  | 3208  | 0       | 0        |\n╰----------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 492756577753412122885\n  Bound result 20000000000000000\n  Bound result 2926\n  Bound result 4000000000000000000\n  Bound result 999990000000000005507\n  Bound result 303621182086975180722\n  Bound result 14000000000000000000\n  Bound result 1\n  Bound result 277921\n  Bound result 177869405932016805702\n  Bound result 345600\n  Bound result 477\n  Bound result 352000\n  Bound result 419052\n  Bound result 684826\n  Bound result 241\n  Bound result 300\n  Bound result 12444106788381585809\n  Bound result 4460\n  Bound result 3707\n  Bound result 864000\n  Bound result 679468276787267423470\n  Bound result 306326229369653598175\n  Bound result 999990000000000002028\n  Bound result 663127993505285995\n  Bound result 20000000000000000\n  Bound result 4809332734644105\n  Bound result 4202047189\n  Bound result 16\n  Bound result 806309868646121634\n  Bound result 241\n  Bound result 999990000000003105580\n  Bound result 245545715275563946478\n  Bound result 57\n  Bound result 10000\n  Bound result 150147068449269422923\n  Bound result 538\n  Bound result 999990000000000003738\n  Bound result 703284478349686088481\n  Bound result 3730\n  Bound result 1800003600\n  Bound result 320178\n  Bound result 345207075289115329620\n  Bound result 791881700872416894187\n  Bound result 999990000000000010001\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.31s (3.31s CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_BondIsTheDepositLessTheFeesPaid\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n[PASS] invariant_VerdictsAreFinal\n PinkyInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| PinkyHandler | answer      | 1804  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | ask         | 1844  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | askAsMaker  | 1831  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | close       | 1818  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | make        | 1855  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | pass        | 1772  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | payout      | 1842  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | refund      | 1755  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | toggleStake | 1863  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3124842406\n  Bound result 44892271466887436219\n  Bound result 12363\n  Bound result 1\n  Bound result 1\n  Bound result 5000000000000000000\n  Bound result 179200\n  Bound result 13241\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1121\n  Bound result 100000\n  Bound result 1\n  Bound result 1\n  Bound result 4825\n  Bound result 1\n  Bound result 45000006401512477221\n  Bound result 2088830\n  Bound result 2\n  Bound result 2\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 1000000000000000000000\n  Bound result 45000000000000003912\n  Bound result 2572717\n  Bound result 1\n  Bound result 5\n  Bound result 1\n  Bound result 2\n  Bound result 2\n  Bound result 45000000000000008739\n  Bound result 603\n  Bound result 4\n  Bound result 1\n  Bound result 1\n  Bound result 4\n  Bound result 8114\n  Bound result 67424\n  Bound result 1\n  Bound result 3\n  Bound result 4\n  Bound result 4\n  Bound result 3\n  Bound result 804983685687725929540\n  Bound result 241139\n  Bound result 10704\n  Bound result 3\n  Bound result 4\n  Bound result 10525\n  Bound result 2694\n  Bound result 2444752364\n  Bound result 1\n  Bound result 1\n  Bound result 73412\n  Bound result 72637\n  Bound result 3\n  Bound result 4\n  Bound result 2\n  Bound result 45000000000002166997\n  Bound result 932578\n  Bound result 3\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.73s (5.73s CPU time)\n\nRan 9 test suites in 5.73s (9.59s CPU time): 120 tests passed, 0 failed, 0 skipped (120 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":63,\"README.md\":115,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":112,\"src/PinkyVault.sol\":385,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/OracleConsumerVectorKey.t.sol\":170,\"test/PinkyInvariant.t.sol\":327,\"test/PinkyStaking.t.sol\":146,\"test/PinkyStakingEdges.t.sol\":300,\"test/PinkyStakingInvariant.t.sol\":168,\"test/PinkyVault.t.sol\":625,\"test/PinkyVaultEdges.t.sol\":979,\"test/mocks/Mocks.sol\":75,\"test/mocks/MoreMocks.sol\":137,\"test/utils/PinkyFixture.sol\":155,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"42e6d91cd0c09a7a183a95898b26ce56bb27833dd2d698eefeb2560a64456c2d","verifiedTreeHash":"a343b1ad1d98c8d6ee37f05301cb42296796cd74","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":19381,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 19.24s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:65:16\n   │\n65 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:168:13\n    │\n168 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:176:24\n    │\n176 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:177:21\n    │\n177 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyStaking.sol:107:28\n    │\n107 │         uint256 leftover = block.timestamp < periodFinish ? (periodFinish - block.timestamp) * rewardRate : 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/PinkyStaking.sol:112:13\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\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/PinkyVault.sol:189:13\n    │\n189 │         if (block.timestamp < v.endTime) revert TooEarly();\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/PinkyStaking.sol:112:30\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:191:22\n    │\n191 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:192:22\n    │\n192 │         v.closedAt = 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/PinkyVault.sol:203:17\n    │\n203 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:247:9\n    │\n247 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:224:21\n    │\n224 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:242:13\n    │\n242 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:273:9\n    │\n273 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:307:9\n    │\n307 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:314:17\n    │\n314 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:319:42\n    │\n319 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:327:79\n    │\n327 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:331:60\n    │\n331 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:335:102\n    │\n335 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":1676,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 496.51µs (430.51µs CPU time)\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 646.45µs (347.91µs CPU time)\n\nRan 11 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 510281, ~: 510142)\nLogs:\n  Bound result 999990000000000518401\n  Bound result 11912\n  Bound result 251\n  Bound result 18446744073709551615\n\n[PASS] test_ADustNotifyDoesNotSlowTheStream() (gas: 1124098)\n[PASS] test_AFlashStakeDuringAPauseEarnsNothing() (gas: 941110)\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 667439)\n[PASS] test_ALargerNotifyStillRaisesTheRateOverSevenDays() (gas: 390492)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 817711)\n[PASS] test_AStreamThatEndedWhilePausedResumesInFull() (gas: 763565)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6599)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77753)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 798034)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 645979)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 11.91ms (14.15ms CPU time)\n\nRan 34 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1655305, ~: 1544847)\nLogs:\n  Bound result 51204043688006522114\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1468179)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1512874)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1447096)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2350842)\n[PASS] test_APriceAboveWhatIsLeftOfTheBondIsStillRefused() (gas: 1384587)\n[PASS] test_APriceRiseAtTheProtocolThePromiseWasMadeUnderIsPaidFromTheBond() (gas: 1637372)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2339788)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2350515)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1472730)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2421697)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1349142)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnderAtAnotherProtocol() (gas: 2275703)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 424318)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1402944)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2064705)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1576797)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1432419)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1551979)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 448491)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1545130)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 336090)\n[PASS] test_MakeRefusesAPromiseWhileTheIntakeQuotesZero() (gas: 619021)\n[PASS] test_MakeRefusesBadInput() (gas: 259241)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 318712)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1327100)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223155)\n[PASS] test_PayoutNeedsAVerdict() (gas: 356765)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 478393)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3132675)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1672772)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1348230)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1355304)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1519902)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 33.18ms (63.13ms CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n PinkyInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| PinkyHandler | answer      | 973   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | ask         | 1060  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | close       | 995   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | make        | 1039  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | pass        | 1005  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | payout      | 1015  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | refund      | 1048  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | toggleStake | 1057  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 9405\n  Bound result 3\n  Bound result 2236\n  Bound result 7089\n  Bound result 45000000000000000019\n  Bound result 1000000\n  Bound result 1\n  Bound result 70400\n  Bound result 869\n  Bound result 1\n  Bound result 10000000000000000\n  Bound result 1\n  Bound result 4\n  Bound result 11352\n  Bound result 1\n  Bound result 45000000000000007795\n  Bound result 172800\n  Bound result 45000000000000010001\n  Bound result 2572717\n  Bound result 45000000000000006140\n  Bound result 2591405\n  Bound result 2\n  Bound result 2\n  Bound result 215198\n  Bound result 1502\n  Bound result 4\n  Bound result 4\n  Bound result 1\n  Bound result 3\n  Bound result 1\n  Bound result 4652\n  Bound result 2989\n  Bound result 1\n  Bound result 3\n  Bound result 2\n  Bound result 2\n  Bound result 45000000000000009312\n  Bound result 2566489\n  Bound result 1\n  Bound result 41966\n  Bound result 1\n  Bound result 4\n  Bound result 3\n  Bound result 5\n  Bound result 4\n  Bound result 5\n  Bound result 1\n  Bound result 1\n  Bound result 45000000003957502253\n  Bound result 1000\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 259200\n  Bound result 4\n  Bound result 45000000000000012386\n  Bound result 603\n  Bound result 5\n  Bound result 45000000000000009349\n  Bound result 6765\n  Bound result 48362394644341218781\n  Bound result 2502914\n  Bound result 9\n  Bound result 9\n  Bound result 3\n  Bound result 100000000000000000000\n  Bound result 1\n  Bound result 9\n  Bound result 4\n  Bound result 4\n  Bound result 1\n  Bound result 2\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.57s (1.57s CPU time)\n\nRan 5 test suites in 1.57s (1.62s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":100,\"README.md\":135,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":127,\"src/PinkyVault.sol\":403,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/PinkyInvariant.t.sol\":219,\"test/PinkyStaking.t.sol\":229,\"test/PinkyVault.t.sol\":689,\"test/mocks/Mocks.sol\":75,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":4102,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/PinkyVault.sol:200: Reentrancy in PinkyVault.ask(uint256) (src/PinkyVault.sol#200-248):\n[medium/medium] divide-before-multiply at src/PinkyStaking.sol:103: PinkyStaking.notify(uint256) (src/PinkyStaking.sol#103-117) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/PinkyStaking.sol:94: PinkyStaking.claim() (src/PinkyStaking.sol#94-100) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PinkyStaking.sol:121: PinkyStaking._resume() (src/PinkyStaking.sol#121-126) uses a dangerous strict equality:\n[low/medium] reentrancy-benign at src/PinkyVault.sol:200: Reentrancy in PinkyVault.ask(uint256) (src/PinkyVault.sol#200-248):\n[low/medium] timestamp at src/PinkyVault.sol:311: PinkyVault.refund(uint256) (src/PinkyVault.sol#311-325) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:121: PinkyStaking._resume() (src/PinkyStaking.sol#121-126) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyVault.sol:200: PinkyVault.ask(uint256) (src/PinkyVault.sol#200-248) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:64: PinkyStaking.lastTimeRewardApplicable() (src/PinkyStaking.sol#64-66) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:103: PinkyStaking.notify(uint256) (src/PinkyStaking.sol#103-117) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyVault.sol:186: PinkyVault.close(uint256) (src/PinkyVault.sol#186-194) uses timestamp for comparisons\n[low/medium] timestamp at src/PinkyStaking.sol:94: PinkyStaking.claim() (src/PinkyStaking.sol#94-100) uses timestamp for comparisons\n[low/medium] timestamp at src/OracleAttestation.sol:157: OracleAttestationConsumer._verifyAttestation(OracleAttestation.Attestation,bytes) (src/OracleAttestation.sol#157-165) uses timestamp for comparisons","passed":true},{"durationMs":651,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PinkyVault.sol:160: Reentrancy: State change after external call (7 places)\n[low] centralization-risk at src/PinkyVault.sol:327: Centralization Risk (3 places)\n[low] internal-function-used-once at src/OracleAttestation.sol:200: Internal Function Used Only Once\n[low] large-numeric-literal at src/LaunchToken.sol:9: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/PinkyStaking.sol:70: Literal Instead of Constant (4 places)\n[low] local-variable-shadowing at src/PinkyVault.sol:47: Local Variable Shadows State Variable (5 places)\n[low] non-reentrant-not-first at src/PinkyVault.sol:327: `nonReentrant` is Not the First Modifier (3 places)","passed":true},{"durationMs":6752,"exitCode":0,"name":"proof ada7abc3f3e3","output":"Compiling 52 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.13s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-2e13d0be/Proof_ada7abc3f3e3.t.sol:ZeroPriceProofTest\n[PASS] test_MakeRefusesAPromiseWhileTheOraclePriceIsZero() (gas: 106431)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 904.12µs (95.76µs CPU time)\n\nRan 1 test suite in 3.69ms (904.12µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":6199,"exitCode":0,"name":"proof d313e09c58c7","output":"2026-10-09T14:20:28.757544Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-qJEgpg/repo/test/imd-proof-2e13d0be/Proof_ada7abc3f3e3.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.57s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-c2b9cb4c/Proof_d313e09c58c7.t.sol:PriceRiseStrandsVerdictTest\n[PASS] test_APriceRiseAtTheSameIntakeMustNotStrandTheVerdict() (gas: 651822)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.17ms (246.43µs CPU time)\n\nRan 1 test suite in 3.63ms (1.17ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"791595ec660550bdc5cbb5ef4b01d265a38665edbc74c34f617818e5ee1ab347","verifiedTreeHash":"ed96057c1c4b5d7a11e910c186c16926c08518a5","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":16389,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 16.27s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:65:16\n   │\n65 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:168:13\n    │\n168 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:176:24\n    │\n176 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:177:21\n    │\n177 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:189:13\n    │\n189 │         if (block.timestamp < v.endTime) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:191:22\n    │\n191 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:192:22\n    │\n192 │         v.closedAt = 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/PinkyStaking.sol:107:28\n    │\n107 │         uint256 leftover = block.timestamp < periodFinish ? (periodFinish - block.timestamp) * rewardRate : 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/PinkyStaking.sol:112:13\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\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/PinkyStaking.sol:112:30\n    │\n112 │         if (leftover != 0 && total / DURATION < rewardRate) period = total / rewardRate;\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/PinkyVault.sol:203:17\n    │\n203 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:247:9\n    │\n247 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:224:21\n    │\n224 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:242:13\n    │\n242 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:273:9\n    │\n273 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:307:9\n    │\n307 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:314:17\n    │\n314 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:319:42\n    │\n319 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:327:79\n    │\n327 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:331:60\n    │\n331 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:335:102\n    │\n335 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":1428,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 571.00µs (328.66µs CPU time)\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 634.14µs (659.87µs CPU time)\n\nRan 11 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 510372, ~: 510362)\nLogs:\n  Bound result 213874574295365830948\n  Bound result 65613\n  Bound result 138871583113703191\n  Bound result 34107605184525589089\n\n[PASS] test_ADustNotifyDoesNotSlowTheStream() (gas: 1124098)\n[PASS] test_AFlashStakeDuringAPauseEarnsNothing() (gas: 941110)\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 667439)\n[PASS] test_ALargerNotifyStillRaisesTheRateOverSevenDays() (gas: 390492)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 817711)\n[PASS] test_AStreamThatEndedWhilePausedResumesInFull() (gas: 763565)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6599)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77753)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 798034)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 645979)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 11.36ms (13.40ms CPU time)\n\nRan 34 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1656671, ~: 1544853)\nLogs:\n  Bound result 28452041324614905717\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1468179)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1512874)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1447096)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2350842)\n[PASS] test_APriceAboveWhatIsLeftOfTheBondIsStillRefused() (gas: 1384587)\n[PASS] test_APriceRiseAtTheProtocolThePromiseWasMadeUnderIsPaidFromTheBond() (gas: 1637372)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2339788)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2350515)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1472730)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2421697)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1349142)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnderAtAnotherProtocol() (gas: 2275703)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 424318)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1402944)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2064705)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1576797)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1432419)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1551979)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 448491)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1545130)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 336090)\n[PASS] test_MakeRefusesAPromiseWhileTheIntakeQuotesZero() (gas: 619021)\n[PASS] test_MakeRefusesBadInput() (gas: 259241)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 318712)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1327100)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223155)\n[PASS] test_PayoutNeedsAVerdict() (gas: 356765)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 478393)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3132675)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1672772)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1348230)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1355304)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1519902)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 34.43ms (64.82ms CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n PinkyInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| PinkyHandler | answer      | 1022  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | ask         | 1040  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | close       | 971   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | make        | 1039  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | pass        | 1046  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | payout      | 1038  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | refund      | 1008  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | toggleStake | 1028  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 45000000000000002308\n  Bound result 2989\n  Bound result 1\n  Bound result 358990\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 369\n  Bound result 100000\n  Bound result 50000000000000000000\n  Bound result 1567706\n  Bound result 2\n  Bound result 1\n  Bound result 1\n  Bound result 2\n  Bound result 1\n  Bound result 2\n  Bound result 10\n  Bound result 10568\n  Bound result 45000000000000006997\n  Bound result 3558\n  Bound result 1\n  Bound result 3\n  Bound result 26562437632431756063\n  Bound result 13052\n  Bound result 2\n  Bound result 164797\n  Bound result 62219\n  Bound result 2\n  Bound result 4\n  Bound result 4\n  Bound result 2\n  Bound result 1\n  Bound result 2184941182687\n  Bound result 4059\n  Bound result 3\n  Bound result 3\n  Bound result 4\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 4\n  Bound result 3\n  Bound result 4\n  Bound result 4\n  Bound result 2\n  Bound result 3\n  Bound result 86400\n  Bound result 2989\n  Bound result 4\n  Bound result 4\n  Bound result 1\n  Bound result 4\n  Bound result 1\n  Bound result 4\n  Bound result 3\n  Bound result 4\n  Bound result 3\n  Bound result 4\n  Bound result 4\n  Bound result 2731\n  Bound result 4078\n  Bound result 1\n  Bound result 38400\n  Bound result 9383\n  Bound result 4\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.33s (1.33s CPU time)\n\nRan 5 test suites in 1.33s (1.38s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":73,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":100,\"README.md\":135,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":127,\"src/PinkyVault.sol\":403,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/PinkyInvariant.t.sol\":219,\"test/PinkyStaking.t.sol\":229,\"test/PinkyVault.t.sol\":689,\"test/mocks/Mocks.sol\":75,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b952fd8456fac332552be186063577731b2c48847547bdae840bebb2a4c2ae5e","verifiedTreeHash":"1362dc1cf60284d0ff20756f8bda78da16e1b2eb","verifierVersion":"0.1.0+c4d32abc"},{"checks":[{"durationMs":14387,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 14.26s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PinkyStaking.sol:62:16\n   │\n62 │         return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n   │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:159:13\n    │\n159 │         if (imd.balanceOf(address(this)) != balanceBefore + bond) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:167:24\n    │\n167 │         v.startBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:168:21\n    │\n168 │         v.endTime = uint64(block.timestamp + duration);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyStaking.sol:103:13\n    │\n103 │         if (block.timestamp >= periodFinish) {\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/PinkyVault.sol:177:13\n    │\n177 │         if (block.timestamp < v.endTime) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:179:22\n    │\n179 │         v.endBlock = uint64(_chainBlock());\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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:180:22\n    │\n180 │         v.closedAt = 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/PinkyVault.sol:191:17\n    │\n191 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:229:9\n    │\n229 │         emit Asked(id, msg.sender, requestId, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PinkyVault.sol:206:21\n    │\n206 │         v.askedAt = 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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PinkyVault.sol:224:13\n    │\n224 │         if (imd.balanceOf(address(this)) + price != balanceBefore) revert InvalidPayment();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:255:9\n    │\n255 │         emit Verdict(id, a.requestId, kept, out);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PinkyVault.sol:289:9\n    │\n289 │         emit Paid(id, 0, bounty, toStakers, burned);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PinkyVault.sol:296:17\n    │\n296 │             if (block.timestamp < uint256(v.askedAt) + ANSWER_TIMEOUT) revert TooEarly();\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/PinkyVault.sol:301:42\n    │\n301 │         if (v.attempts < MAX_ATTEMPTS && block.timestamp < uint256(v.closedAt) + REFUND_GRACE) revert TooEarly();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:309:79\n    │\n309 │     function setProtocol(address intake_, bytes32 action_) external onlyOwner nonReentrant {\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:313:60\n    │\n313 │     function setSigner(address signer_) external onlyOwner nonReentrant {\n    │                                                            ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/PinkyVault.sol:317:102\n    │\n317 │     function setPanel(uint16 panelSize_, uint16 quorum_, uint32 validForSeconds_) external onlyOwner nonReentrant {\n    │                                                                                                      ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\n","passed":true},{"durationMs":1505,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/OracleConsumerConformance.t.sol:OracleConsumerConformanceTest\n[PASS] test_AcceptsTheProtocolSignature() (gas: 19823)\n[PASS] test_CallbackSelectorIsCanonical() (gas: 4388)\n[PASS] test_DigestMatchesTheProtocol() (gas: 13594)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 574.49µs (317.81µs CPU time)\n\nRan 4 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] test_MintsTheWholeFixedSupplyToTheDeployer() (gas: 27297)\n[PASS] test_NameSymbolAndDecimals() (gas: 27580)\n[PASS] test_NothingCanMintAfterLaunch() (gas: 35871)\n[PASS] test_TransferMovesExactlyWhatItWasAsked() (gas: 83966)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 757.92µs (537.47µs CPU time)\n\nRan 7 tests for test/PinkyStaking.t.sol:PinkyStakingTest\n[PASS] testFuzz_NothingIsStrandedWhenTheStreamEnds(uint256,uint256,uint256,uint256) (runs: 256, μ: 494956, ~: 495138)\nLogs:\n  Bound result 488213911864351681141\n  Bound result 2\n  Bound result 4\n  Bound result 20863\n\n[PASS] test_AGapInTheMiddleOfTheStreamIsNotLostEither() (gas: 597821)\n[PASS] test_ANotifyAfterAGapStillAccountsForEverything() (gas: 807662)\n[PASS] test_ConstructorRefusesBadInput() (gas: 6511)\n[PASS] test_NotifyNeedsStakersAndARealAmount() (gas: 77665)\n[PASS] test_StreamIsSharedByStakeOverTime() (gas: 792670)\n[PASS] test_StreamSurvivesAPeriodWithNobodyStaked() (gas: 630762)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 27.87ms (24.06ms CPU time)\n\nRan 31 tests for test/PinkyVault.t.sol:PinkyVaultTest\n[PASS] testFuzz_EveryPathLeavesTheVaultEmpty(uint256,uint256,uint256,bool) (runs: 256, μ: 1621274, ~: 1517442)\nLogs:\n  Bound result 95000000125817815349\n\n[PASS] test_ALateAnswerStillCountsUntilSomeoneMovesOn() (gas: 1440889)\n[PASS] test_AMakerWhoAsksAboutTheirOwnBrokenPromiseGetsNoBounty() (gas: 1485584)\n[PASS] test_AMakerWhoAsksAboutTheirOwnKeptPromiseStillGetsTheBond() (gas: 1419806)\n[PASS] test_AMakerWhoAsksFirstYieldsTheBountyToTheWatcherWhoReasks() (gas: 2323480)\n[PASS] test_ARequestBoughtWithASmallerPanelIsStillAnswerable() (gas: 2310174)\n[PASS] test_AThirdPartyReaskDoesNotTakeTheWatchersBounty() (gas: 2323141)\n[PASS] test_AnAnswerCannotBeDeliveredTwice() (gas: 1445462)\n[PASS] test_AskAgainAfterATimeoutAndEitherAnswerCounts() (gas: 2394342)\n[PASS] test_AskPaysTheIntakeFromTheBond() (gas: 1321846)\n[PASS] test_AskRefusesAPriceAboveTheOneTheBondWasMadeUnder() (gas: 2235970)\n[PASS] test_AskWaitsForTheSettleDelay() (gas: 379972)\n[PASS] test_BodyIsTheJsonTheOracleExpects() (gas: 1375654)\n[PASS] test_BrokenPromiseSharesWithStakers() (gas: 2032584)\n[PASS] test_BrokenPromiseWithNoStakersPaysTheAskerAndBurnsTheRest() (gas: 1549545)\n[PASS] test_CallbackFitsTheIntakeStipendFromColdStorage() (gas: 1405129)\n[PASS] test_ChangingThePanelDuringARequestDoesNotRefuseItsAnswer() (gas: 1522362)\n[PASS] test_CloseOnlyAfterTheDeadlineAndOnlyOnce() (gas: 404173)\n[PASS] test_KeptPromiseReturnsTheBond() (gas: 1517818)\n[PASS] test_MakePullsTheBondAndRecordsTheBlock() (gas: 291750)\n[PASS] test_MakeRefusesBadInput() (gas: 259179)\n[PASS] test_MakeUsesTheArbitrumBlockNumberWhereThereIsOne() (gas: 274394)\n[PASS] test_OnlyTheIntakeThatWasAskedCanAnswer() (gas: 1299810)\n[PASS] test_OnlyTheOwnerChangesSettings() (gas: 223111)\n[PASS] test_PayoutNeedsAVerdict() (gas: 312403)\n[PASS] test_RefundAfterTheGraceWhenNobodyAsked() (gas: 434049)\n[PASS] test_RefundAfterThreeUnansweredAttempts() (gas: 3105236)\n[PASS] test_RefusesAnAnswerForAnotherBlockRange() (gas: 1645504)\n[PASS] test_RefusesAnAnswerFromAnotherSigner() (gas: 1320962)\n[PASS] test_RefusesAnAnswerOfAnotherType() (gas: 1327992)\n[PASS] test_RefusesAnAnswerThePanelDidNotAgreeOn() (gas: 1492634)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 30.17ms (55.82ms CPU time)\n\nRan 1 test for test/PinkyInvariant.t.sol:PinkyInvariantTest\n[PASS]\nPinkyInvariantTest invariants:\n[PASS] invariant_EveryTokenIsAccountedFor\n[PASS] invariant_SettledPromisesAreEmpty\n[PASS] invariant_VaultHoldsExactlyTheOpenBonds\n PinkyInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| PinkyHandler | answer      | 1065  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | ask         | 1019  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | close       | 1052  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | make        | 1031  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | pass        | 1014  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | payout      | 983   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | refund      | 993   | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| PinkyHandler | toggleStake | 1035  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 45000000000000006560\n  Bound result 1288155\n  Bound result 50000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 172800\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 928\n  Bound result 9600\n  Bound result 1\n  Bound result 1\n  Bound result 100\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 57792\n  Bound result 71264\n  Bound result 1\n  Bound result 86400\n  Bound result 600\n  Bound result 1\n  Bound result 1247\n  Bound result 4869\n  Bound result 6400\n  Bound result 69254\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 3200\n  Bound result 7496\n  Bound result 45000000000663670243\n  Bound result 11389\n  Bound result 45000000000000165596\n  Bound result 177886\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 259200\n  Bound result 71019\n  Bound result 44838776911245967747\n  Bound result 1719\n  Bound result 4\n  Bound result 3\n  Bound result 45000000000000002320\n  Bound result 1968601\n  Bound result 540\n  Bound result 5\n  Bound result 11624\n  Bound result 100000\n  Bound result 5\n  Bound result 1\n  Bound result 3\n  Bound result 5\n  Bound result 5\n  Bound result 5\n  Bound result 3\n  Bound result 45000000000000004370\n  Bound result 5612\n  Bound result 5\n  Bound result 3000000000000000000\n  Bound result 11287620217390961939\n  Bound result 2211950\n  Bound result 239293\n  Bound result 161\n  Bound result 45000000000000000028\n  Bound result 2588287\n  Bound result 46000000000000000001\n  Bound result 6241\n  Bound result 85115\n  Bound result 18\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.40s (1.40s CPU time)\n\nRan 5 test suites in 1.40s (1.46s CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"PinkyStaking.claim()\",\"PinkyStaking.notify(uint256)\",\"PinkyStaking.stake(uint256)\",\"PinkyStaking.unstake(uint256)\",\"PinkyVault.ask(uint256)\",\"PinkyVault.close(uint256)\",\"PinkyVault.make(address,uint256,uint256,uint256)\",\"PinkyVault.onOracleResult(bytes32,(bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PinkyVault.payout(uint256)\",\"PinkyVault.refund(uint256)\",\"PinkyVault.setPanel(uint16,uint16,uint32)\",\"PinkyVault.setProtocol(address,bytes32)\",\"PinkyVault.setSigner(address)\"],\"files\":{\".gitignore\":3,\"ADAPTATION.md\":63,\"README.md\":115,\"brand/BRAND.md\":37,\"brand/avatar.png\":13,\"brand/avatar.svg\":8,\"brand/banner.png\":116,\"brand/banner.svg\":15,\"foundry.toml\":21,\"launch.json\":40,\"requests/launch.input.json\":1,\"requests/oracle-body.example.json\":1,\"requests/oracle-test.body.json\":1,\"src/LaunchToken.sol\":11,\"src/OracleAttestation.sol\":221,\"src/PinkyStaking.sol\":112,\"src/PinkyVault.sol\":385,\"src/interfaces/IIntake.sol\":15,\"test/LaunchToken.t.sol\":40,\"test/OracleConsumerConformance.t.sol\":98,\"test/PinkyInvariant.t.sol\":219,\"test/PinkyStaking.t.sol\":146,\"test/PinkyVault.t.sol\":625,\"test/mocks/Mocks.sol\":75,\"tools/oracle-test.calldata.json\":10,\"tools/oracle-test.html\":145,\"tools/serve.mjs\":22},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"dfd64ac9d3e0226f8b2480b1a4c1c3d6bb5167f2a2b7419f604a9230586b210a","verifiedTreeHash":"ac0f5a176592015dadf4a22ccccebd327908ec6f","verifierVersion":"0.1.0+c4d32abc"}]}