{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"49c8d9f7-82c7-4894-8a11-d4d52cfbb206","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"db4b36dc4d57ac7de12d9b0b52aecd39253eae6aa2a1c6ebf5ec242a21311a8e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"0454e62cf87ab07c9fd5deb033d7d3e60a7d68c6fd1e192cd4c66d3a07548a4d","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"5e66e8c1a19209efce09f9487ed8565f9186f3d89b486c6165a55bba4dea970c","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"40ea4618a01f32d9ad6d904555938aebc163fd90a2a23c1d34c91a65d2892213","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d3101d2b2b310d4c2dfd5e70c0fedcfed1547e71acd309d5d5c4f47f5f0d20ad","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3d5aeb2b781bbbe6394a7336dd7dbf1ae57b9b5d2286a1b4ff9324dab759400a","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"c4bd43509e4d2d6cde96e36d89648a38221cbc4dbb93975cdf05f22697f568c6","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"9ad8484a66bc484295a7539d399514bbf512f26c76ae0475be2df03030ead441","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"A custom token: keptonchain (KEPT).\nToken name: keptonchain\nToken symbol: KEPT\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\nWhat it does: keptmd.vercel.app","parentJobId":null,"planHash":"0b5cf2442f5afd2e78e7fc98311433e29ee9947081e679fdedfbf99764a95118","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"49c8d9f7-82c7-4894-8a11-d4d52cfbb206","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2640-keptonchain"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51162","feedbackHash":"0b570c091bc641aed778fb3c8cd0a80541f633ab6fd242ad9bb7a6f36ae983ea","nodeKey":"audit_economics","submissionHash":"db4b36dc4d57ac7de12d9b0b52aecd39253eae6aa2a1c6ebf5ec242a21311a8e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51451","feedbackHash":"4437e868954bfeee4d0677bd66737e7ff04a13fdac157ff49ed943f97af38b38","nodeKey":"audit_flow","submissionHash":"0454e62cf87ab07c9fd5deb033d7d3e60a7d68c6fd1e192cd4c66d3a07548a4d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51474","feedbackHash":"c6657bc92da4cc6d6e23a9d6ecc243595def0706e47e14780fb73264416ff5a1","nodeKey":"audit_judge","submissionHash":"5e66e8c1a19209efce09f9487ed8565f9186f3d89b486c6165a55bba4dea970c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51493","feedbackHash":"0331dc3813c3683b2eca9d8aee3b66bae89e9b3a683df0e163b8f2702e2e8603","nodeKey":"audit_judge","submissionHash":"229bf921a739ba0cf7c642c3a3d5e0db9fd11c5a8934fe321b5db370e15863f3","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51428","feedbackHash":"6447dec99e75408bb12ce862276e8a29f5b899f6065f22c3f277607c76701844","nodeKey":"audit_math","submissionHash":"40ea4618a01f32d9ad6d904555938aebc163fd90a2a23c1d34c91a65d2892213","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51886","feedbackHash":"bb5b09a052313af72da75f7515cacd1f06d4648fcc945b397710e4dc5df568c1","nodeKey":"audit_permissions","submissionHash":"d3101d2b2b310d4c2dfd5e70c0fedcfed1547e71acd309d5d5c4f47f5f0d20ad","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51485","feedbackHash":"397d2e0e39f7684a186d078a77fc6d70d78087d85b32867fac53e847f71a655f","nodeKey":"build_contract_project","submissionHash":"3d5aeb2b781bbbe6394a7336dd7dbf1ae57b9b5d2286a1b4ff9324dab759400a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51016","feedbackHash":"6db1e5dd955d227cd936c3e7aeb2744782addd5577f112363618e0a56606e0fb","nodeKey":"build_contract_project","submissionHash":"4f47d3720ab9a3cd5555e2e0d92c61862761249b8e8eb873ebb373d90c7f2e4c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51481","feedbackHash":"317b8b649938206409ee3b542bd58d92b0878fd65f64259c2e5d02c021efe2a4","nodeKey":"manifest","submissionHash":"c4bd43509e4d2d6cde96e36d89648a38221cbc4dbb93975cdf05f22697f568c6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51509","feedbackHash":"4704b57b7a4afb92e6d9b233e09c87f707fd958471c36b2a5fe3cd557f957d69","nodeKey":"manifest","submissionHash":"1d3984f151406964a727f5a67764141f5d1beeb85fd9bde09e001954ea14fdb1","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52207","feedbackHash":"1e0b04012348bacc693f32a9429448d2f8406d7b0678bdea408c6c6323c1efdf","nodeKey":"write_foundry_tests","submissionHash":"9ad8484a66bc484295a7539d399514bbf512f26c76ae0475be2df03030ead441","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50945","feedbackHash":"116969cb687146ab50ad5d8dcaedcc5b7829e4a115f80d22b82230a684ed75af","nodeKey":"write_foundry_tests","submissionHash":"ab68a0b939aed021ffa7aa6c57e957404276a87318a43ff504fda964964bf32e","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"a56d3e285ce36fb62b89d8a7e6a7db8458d5c4fcf91da47665ac10cc41a457d4","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"79373c79d1351eba","findings":[{"citation":"resolved","description":"createPledge only checks that a deadline is in the future; it has no upper bound. requestCheck (line 295), submitVerdict (line 325) and settleMissed (line 353) all compute `m.deadline + GRACE` in uint64 (both operands are uint64, GRACE = 3 days). For any deadline greater than type(uint64).max - 259200 that addition reverts with Panic(0x11) on every call, so the milestone can be neither checked, kept nor burned, `p.settled` never reaches `p.count`, and reclaimBudget reverts NotSettled forever. The tranche and the whole IMD budget (including anything strangers added with addBudget) are locked permanently, which contradicts the contract's own rule that locked tokens move on a verdict or a missed deadline. A pledge's creator and beneficiary are distinct parties, so a typo or a bad front-end value in a single milestone deadline strands the beneficiary's tranches of every other milestone of that pledge as well as the shared budget. Fix: reject deadlines above `type(uint64).max - GRACE` in createPledge (or do the comparison in uint256: `block.timestamp > uint256(m.deadline) + GRACE`).","line":238,"path":"src/PledgeVault.sol","reproduction":"1. Configure the oracle (any intake, price 0.5e18, one signer, quorum 1). 2. Creator approves the vault and calls createPledge(KEPT, beneficiary, \"trap\", [{check:\"shipped\", deadline: type(uint64).max, amount: 1000e18}], budget 1e18). Expected: either revert, or a pledge that can eventually be kept or burned. Actual: createPledge succeeds and locks 1000 KEPT and 1 IMD. beneficiary.requestCheck(id, 0) reverts with Panic(0x11) arithmetic overflow at line 295; settleMissed(id, 0) reverts with Panic(0x11) at line 353 at any block.timestamp including type(uint64).max; reclaimBudget(id) reverts NotSettled. Verified with a scratch Foundry test (test/scratch/DeadlineOverflow.t.sol): all three reverts observed on the current code.","severity":"low","snippet":"            if (m.deadline <= block.timestamp) revert DeadlinePassed();","title":"createPledge accepts a deadline within GRACE of type(uint64).max; every later path overflows, so the milestone can never settle and the budget is stuck"},{"citation":"resolved","description":"The release of a kept tranche (line 337) and the Referee Fund refund (line 341) sit in the same transaction with no isolation. setRefereeFund (line 426) accepts any contract whose vault() returns this vault, once, irrevocably. Two failure modes follow. (a) If `refund` reverts, every kept verdict reverts; once the milestone's grace period passes, settleMissed burns a tranche the panel had certified as kept. (b) The returned value is credited to `p.budget` without checking that IMD actually arrived (`_pullExact` does that check for deposits, the refund path does not), so a fund that returns more than it transfers inflates one pledge's budget, and reclaimBudget then pays that creator IMD that belongs to other pledges' budgets. Both require the owner to point the vault at something other than the launched RefereeFund, so this is a trust-assumption gap rather than an open exploit; the README states the owner \"never touches locked tokens or budgets\", which this path contradicts. The real IMD token (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, a LayerZero OFT ERC-20 with no pause, blocklist or tax, checked on mainnet) cannot itself make the refund leg fail. Fix: measure the vault's IMD balance before and after `fund.refund` and credit only the delta (capped at `paid`), and call it in a try/catch (or after the release in a separate best-effort step) so a refund failure never blocks or reverts a kept release.","line":341,"path":"src/PledgeVault.sol","reproduction":"Setup: vault configured (price 0.5e18, one signer, quorum 1). Owner calls setRefereeFund(F) where F is a contract with vault() == address(vault) and refund(uint256) that transfers nothing and returns a stored value. Team A creates pledge a (1 milestone, 100e18 KEPT, budget 1e18 IMD); team B creates pledge b (budget 5e18 IMD). Vault holds 6e18 IMD. A requests a check (0.5e18 goes to intake; vault holds 5.5e18). F is set to return 5e18. submitVerdict(a, 0, 1, true, expiry, [sig]) succeeds: expected p.budget(a) = 0.5e18 and vault IMD unchanged; actual p.budget(a) = 5.5e18 while the vault still holds 5.5e18, all of it owed to pledge b except 0.5e18. reclaimBudget(a) transfers 5.5e18 IMD to team A; vault IMD = 0; pledge b's budget still reads 5e18 and b.requestCheck reverts for lack of IMD. Second variant: F.refund reverts; submitVerdict(a, 0, 1, true, ...) with valid panel signatures reverts; after deadline + 3 days + 1, settleMissed(a, 0) burns the 100e18 tranche. Both sequences verified with test/scratch/FundTrust.t.sol on the current code.","severity":"low","snippet":"            uint256 refunded = fund.refund(paid);","title":"submitVerdict's kept path trusts RefereeFund.refund blindly: a fund that reverts turns delivered milestones into burns, a fund that over-reports lets one pledge reclaim other pledges' IMD budgets"},{"citation":"resolved","description":"The launch manifest resolves only `$token`, `$requester` (economics.remainderTo) and `$contract:EarlierName` in application contracts' constructorArgs (plus static words). README lines 14 and 64 instruct the manifest step to fill PledgeVault's `owner_` with `$owner`, which is not a recognised placeholder; a launch.json written from the README would be refused or would deploy with an unresolved value. No code change is needed: the manifest should carry `[\"$requester\", \"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]` for PledgeVault and `[\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\", \"$contract:PledgeVault\"]` for RefereeFund, and the README should say so.","line":14,"path":"README.md","reproduction":"Write launch.json with contracts[0] = {\"contract\":\"PledgeVault\",\"constructorArgs\":[\"$owner\",\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]} as the README directs. Expected: the vault's owner is the requester. Actual: `$owner` is outside the schema's placeholder set, so the manifest is not deployable as written.","severity":"info","snippet":"| `PledgeVault` | Locks any plain ERC-20 behind up to eight milestones; verifies the swarm's EIP-712 verdicts; releases kept tranches, burns missed ones; holds IMD check budgets; owner-configured oracle behind a seven-day notice. | `address owner_` (`$owner`), `address imd_` (IMD, `0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7`) |","title":"README names `$owner` as the manifest placeholder for PledgeVault's owner_; the manifest vocabulary has no such placeholder"},{"citation":"resolved","description":"By design createPledge works before scheduleOracleConfig has ever run, and the README encourages locking KEPT's own roadmap on deployment day. Two owner-side states then have no remedy for existing pledges: (1) `renounceOwnership` is inherited from Ownable and not disabled, so if the owner renounces (or loses the key) while `intake == address(0)`, requestCheck reverts OracleNotConfigured forever, no verdict can ever be submitted, and every locked tranche burns at deadline + 3 days with no way for a beneficiary to prove delivery. (2) Line 388 makes the first configuration apply immediately (`intake == address(0) ? block.timestamp : block.timestamp + NOTICE`), so pledges created before it get no seven-day notice of the panel that will judge them, contrary to the README's \"nothing to protect yet\". Neither is an exploit by an unprivileged party; both are trust assumptions the README should state plainly. Optional hardening: override renounceOwnership to revert, and apply the notice whenever `pledgeCount > 0`.","line":293,"path":"src/PledgeVault.sol","reproduction":"State: fresh vault, intake == 0. Team calls createPledge(KEPT, beneficiary, ..., 1 milestone deadline T, amount 1000e18, budget 1e18). Owner calls renounceOwnership(). Expected (per README): tokens move only on a verdict or a missed deadline, and a delivered milestone can be proven. Actual: beneficiary.requestCheck reverts OracleNotConfigured at line 293; scheduleOracleConfig reverts OwnableUnauthorizedAccount for everyone; at T + 3 days + 1 anyone calls settleMissed and the 1000e18 tranche burns regardless of delivery.","severity":"info","snippet":"        if (intake == address(0)) revert OracleNotConfigured();","title":"Pledges may be created before the oracle exists, but if the owner renounces or never configures, every tranche can only burn; the first configuration also applies with no notice to pledges already loc"}],"hash":"0454e62cf87ab07c9fd5deb033d7d3e60a7d68c6fd1e192cd4c66d3a07548a4d","nodeId":"f6f4fa96-608d-4b74-ae46-a5420e505445","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written and validated: all four snippets match their cited lines, and all 21 entry points plus four invariants have coverage rows.\n\n**Outcome.** The control-flow area of the KEPT launch is sound: no critical or high defect. The token is plain OpenZeppelin ERC-20 with burn and permit. The vault's verdict path is replay-safe, its state writes precede external calls, its grace and verdict windows do not overlap, and the real IMD token on mainnet is a LayerZero OFT with no pause, blocklist or tax, so the IMD legs cannot be frozen from outside. Two low and two informational findings are reported, each with a concrete reproduction, and the two low ones were confirmed with scratch Foundry tests.\n\n- **Low, createPledge line 238.** No upper bound on a milestone deadline. A deadline within 3 days of `type(uint64).max` makes every later path overflow, so the milestone can never be checked, kept or burned and the budget can never be reclaimed.\n- **Low, submitVerdict line 341.** The kept release and the Referee Fund refund are coupled with no isolation or arrival check. A fund set by the owner that reverts turns certified-kept milestones into burns. One that over-reports lets one pledge's creator reclaim other pledges' IMD. This needs the owner to set a non-canonical fund, so it is reported as a trust-assumption gap.\n- **Info, README line 14.** The README tells the manifest step to use `$owner`, which is not a manifest placeholder. The vault's owner argument should be `$requester`.\n- **Info, PledgeVault line 293.** Pledges can be locked before the oracle exists, but `renounceOwnership` is not disabled and the first configuration applies with no notice. Both are owner-side states worth stating plainly as trust assumptions.\n\n**Coverage.** All three guides were applied by me directly as passes with no helpers. I did not attach proof tests, since none of the findings reaches high severity. Slither's and aderyn's medium-or-higher lines were all checked: the uninitialized-state and uninitialized-local lines are false positives, the strict equalities are sentinel checks on zero, and the reentrancy line is covered by the reentrancy guard and the trusted fund. The scratch tests under `test/scratch/` are mine and nothing there is part of the submission.","treeHash":null,"usage":{"cachedInputTokens":1276393,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":40944,"runtime":"claude","turns":32,"wallClockMs":577785}},{"artifacts":[],"attempt":1,"bundleHash":"9be66fdf81e610ff9099a05692b11f4e75e444b95d5757850f9ece73b495c7d1","device":"74a99f640688d37b","findings":[],"hash":"1d3984f151406964a727f5a67764141f5d1beeb85fd9bde09e001954ea14fdb1","nodeId":"5b18d856-423c-4c17-bd72-f36ef7e16fb9","outcome":"completed","summary":"`launch.json` is written and verified. It is the only changed file in the tree.\n\n**What the manifest describes**\n\n- **Token** `KeptToken`, name keptonchain, symbol KEPT, 18 decimals, empty constructorArgs, totalSupply of one billion in minor units. This matches the constructor, which takes no parameters and mints the fixed supply once to msg.sender.\n- **PledgeVault** takes the requester as owner, written as `$requester`, and the IMD address as its paired currency. The owner is the remainderTo address, so the owners check that forbids factory or IMD ownership is satisfied.\n- **RefereeFund** takes the IMD address and `$contract:PledgeVault`, listed after the vault so the dependency resolves.\n- **pool and economics** are copied verbatim from the launch reference: IMD as pairedCurrency, fee 12500, tickSpacing 60, the one-to-one initialPrice, and the requester's economics object.\n- **notes** explain the token, both contracts, the owner's post-launch configuration calls, and that no contract receives KEPT at launch.\n\n**Verification**\n\n- `forge build` succeeds and `forge test` with the verifier's fuzz seed passes all 54 tests.\n- A scratch script checked the manifest against the compiled ABIs: schema keys only, one argument per constructor parameter in order, unique names under 32 characters, lowercase nonzero addresses, and notes under 4000 characters.\n- `git status` shows only the new launch.json.\n\nOne detail worth knowing: the README refers to the vault's owner placeholder as `$owner`, which is not a manifest placeholder. The manifest uses `$requester`, which the schema defines as economics.remainderTo, so the owner is the requester's address as intended.","treeHash":"38a70bfb488d09270d2134a3ec3088c71ab35a0c","usage":{"cachedInputTokens":173162,"inputTokens":130,"model":"claude-fable-5-1","outputTokens":4692,"runtime":"claude","turns":11,"wallClockMs":68864}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"74979c69cdd583ad","findings":[{"citation":"resolved","description":"Merged from four reports with one root cause (audit_permissions medium, write_foundry_tests medium, audit_economics low, audit_flow low). setRefereeFund (line 429) checks only `RefereeFund(fund).vault() != address(this)`, never `fund.imd() == imd`, and can run only once (FundAlreadySet, line 428), so a mismatch is permanent. submitVerdict then credits `p.budget += refunded` (line 342) from the fund's return value without measuring IMD received, unlike every other inflow, which goes through _pullExact. Paths: (a) honest misconfiguration: the project's own RefereeFund deployed with the wrong imd_ is accepted, and each kept verdict credits up to 0.5 IMD the vault never got; (b) a malicious or compromised owner installs a contract with vault()==vault whose refund() returns an arbitrary number, then reclaims every pledge's IMD through a self-pledge; or one whose refund() reverts, which makes every kept verdict revert so that certified-kept tranches burn via settleMissed after grace. (b) contradicts the stated guarantee 'The owner never touches locked tokens or budgets' (PledgeVault.sol:28-29, README.md:59-60). Victims: other pledges' creators, whose reclaimBudget comes up short. Medium: loss under a specific precondition plus a broken guarantee. Fix preserving design: in setRefereeFund also require `address(RefereeFund(fund).imd()) == address(imd)`; in submitVerdict credit the measured IMD balance delta around fund.refund (capped at paid), and optionally isolate the refund in try/catch so a refund failure cannot block a kept release.","line":341,"path":"src/PledgeVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {KeptToken} from \"src/KeptToken.sol\";\nimport {PledgeVault} from \"src/PledgeVault.sol\";\nimport {RefereeFund} from \"src/RefereeFund.sol\";\n\ncontract PlainToken is ERC20 {\n    constructor(string memory n, string memory s) ERC20(n, s) {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice The vault credits `fund.refund(paid)`'s return value to a pledge budget without measuring the IMD\n/// that actually arrived, and `setRefereeFund` accepts any fund whose `vault()` is this vault regardless of\n/// which token the fund holds. An unmodified RefereeFund built with a different `imd_` therefore pays the\n/// vault a worthless token while the vault credits real IMD; the refunded pledge then reclaims IMD that\n/// belongs to another pledge's budget, and that other pledge can no longer reclaim in full.\n/// Passes once the vault either refuses a fund whose `imd()` differs from its own, or credits only the\n/// IMD balance delta the refund produced.\ncontract RefundCreditedByReturnValueTest is Test {\n    uint256 constant PRICE = 0.5e18;\n    uint256 constant T0 = 1_800_000_000;\n\n    KeptToken kept;\n    PlainToken imd;\n    PlainToken other;\n    PledgeVault vault;\n    RefereeFund fund;\n\n    address owner = makeAddr(\"owner\");\n    address teamA = makeAddr(\"teamA\");\n    address teamB = makeAddr(\"teamB\");\n    address intake = makeAddr(\"intake\");\n    address signer;\n    uint256 signerKey;\n\n    function setUp() public {\n        vm.warp(T0);\n        kept = new KeptToken();\n        imd = new PlainToken(\"IMD\", \"IMD\");\n        other = new PlainToken(\"Other\", \"OTH\");\n        vault = new PledgeVault(owner, address(imd));\n        // The project's own RefereeFund, pointed at this vault but holding a different token.\n        fund = new RefereeFund(address(other), address(vault));\n\n        (signer, signerKey) = makeAddrAndKey(\"panel\");\n        address[] memory panel = new address[](1);\n        panel[0] = signer;\n        vm.startPrank(owner);\n        vault.scheduleOracleConfig(intake, PRICE, panel, 1);\n        vault.applyOracleConfig();\n        // Accepted on the current code: only vault() is checked, never imd(). A fixed vault may refuse it.\n        try vault.setRefereeFund(address(fund)) {} catch {}\n        vm.stopPrank();\n\n        other.mint(address(this), 10e18);\n        other.approve(address(fund), 10e18);\n        fund.donate(10e18);\n\n        kept.transfer(teamA, 1_000e18);\n        kept.transfer(teamB, 1_000e18);\n        imd.mint(teamA, 10e18);\n        imd.mint(teamB, PRICE);\n        vm.startPrank(teamA);\n        kept.approve(address(vault), type(uint256).max);\n        imd.approve(address(vault), type(uint256).max);\n        vm.stopPrank();\n        vm.startPrank(teamB);\n        kept.approve(address(vault), type(uint256).max);\n        imd.approve(address(vault), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _one(uint256 amount) internal view returns (PledgeVault.MilestoneInput[] memory ms) {\n        ms = new PledgeVault.MilestoneInput[](1);\n        ms[0] = PledgeVault.MilestoneInput({check: \"shipped\", deadline: uint64(block.timestamp + 30 days), amount: amount});\n    }\n\n    function test_keptVerdictCreditsImdTheVaultNeverReceived() public {\n        // Team A locks a budget of 10 IMD. Team B locks 0.5 IMD, exactly one check.\n        vm.prank(teamA);\n        uint256 a = vault.createPledge(address(kept), teamA, \"A\", _one(1_000e18), 10e18);\n        vm.prank(teamB);\n        uint256 b = vault.createPledge(address(kept), teamB, \"B\", _one(1e18), PRICE);\n        assertEq(imd.balanceOf(address(vault)), 10e18 + PRICE);\n\n        // B asks for a check (0.5 IMD to the intake) and the panel finds the milestone kept.\n        vm.prank(teamB);\n        vault.requestCheck(b, 0);\n        uint256 expiry = block.timestamp + 1 hours;\n        bytes32 digest = vault.verdictDigest(b, 0, 1, true, expiry);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerKey, digest);\n        bytes[] memory sigs = new bytes[](1);\n        sigs[0] = abi.encodePacked(r, s, v);\n        vault.submitVerdict(b, 0, 1, true, expiry, sigs);\n\n        // No IMD arrived with the refund (the fund holds OTH), so the vault's IMD is exactly A's budget.\n        assertEq(imd.balanceOf(address(vault)), 10e18, \"no IMD arrived with the refund\");\n\n        // Expected: B's budget stays 0 because the vault received no IMD. Actual: credited 0.5 IMD.\n        uint256 bBudget = vault.getPledge(b).budget;\n        assertEq(bBudget, 0, \"budget credited with IMD the vault never received\");\n\n        // Expected: A reclaims its whole 10 IMD once its milestone settles.\n        if (bBudget > 0) vault.reclaimBudget(b);\n        vm.warp(T0 + 30 days + 3 days + 1);\n        vault.settleMissed(a, 0);\n        try vault.reclaimBudget(a) {} catch {}\n        assertEq(imd.balanceOf(teamA), 10e18, \"team A did not get its whole budget back\");\n        assertEq(vault.getPledge(a).budget, 0, \"team A's budget is stuck in the vault\");\n    }\n}","reproduction":"Reproduced: forge test --match-path test/scratch/Proof_25bf54b8863d.t.sol fails with 'budget credited with IMD the vault never received: 500000000000000000 != 0'; the tester's Proof_2504146ff7f7 fails likewise ('0 < 500000000000000000'). State: vault=PledgeVault(owner, IMD); fund=RefereeFund(OTHER, vault); owner scheduleOracleConfig(intake, 0.5e18, [signer], 1), applyOracleConfig, setRefereeFund(fund) succeeds. Donate 10e18 OTHER to fund. Team A createPledge(KEPT, A, 'A', [1 milestone 1000e18, +30d], 10e18 IMD); team B createPledge(KEPT, B, 'B', [1 milestone 1e18], 0.5e18 IMD); vault IMD 10.5e18. B requestCheck(b,0) -> vault IMD 10e18. submitVerdict(b,0,1,true,expiry,[sig]). Expected: B budget 0 (no IMD arrived). Actual: B budget 0.5e18, vault IMD 10e18; reclaimBudget(b) pays 0.5e18 of A's IMD; after settleMissed(a,0), reclaimBudget(a) reverts ERC20InsufficientBalance (9.5e18 < 10e18).","severity":"medium","snippet":"            uint256 refunded = fund.refund(paid);","title":"Kept-verdict refund is credited from RefereeFund.refund's return value, and setRefereeFund never checks the fund's token: a mismatched or owner-chosen fund creates unbacked IMD budgets that drain othe"},{"citation":"resolved","description":"From audit_math (reported medium; recalibrated to low). _pullExact (line 497-502) only checks one transfer's delivered amount, so tokens whose holder balances can decrease later (negative rebase, demurrage, issuer clawback) are accepted, contrary to KeptToken.sol:13's claim that the vault 'refuses tokens with taxes, rebasing or balance decay'. All pledges of a token share the vault's balance and each kept tranche pays fixed m.amount, so the whole shortfall falls on whichever pledges are kept last; those kept verdicts revert and the tranche burns/strands after grace. Low rather than medium: KEPT and plain tokens are unaffected, PledgeVault.sol:207 and README.md:122 tell teams to use plain ERC-20s only, and harm requires a third party to pledge an abnormal token. Fix: correct the KeptToken.sol:13 claim, or track per-token locked totals and pay pro rata.","line":337,"path":"src/PledgeVault.sol","reproduction":"Reproduced: forge test --match-path test/scratch/Proof_7d3d4d0daf25.t.sol fails with ERC20InsufficientBalance(vault, 9e19, 1e20). DecayToken = OZ ERC20 with exact transfers plus decay(who,a)=>_burn(who,a). Pledges A and B each lock 1 milestone of 100e18 (vault 200e18). decay(vault,10e18) -> 190e18. Kept verdict on A pays 100e18 (vault 90e18). Kept verdict on B reverts ERC20InsufficientBalance(vault, 90e18, 100e18). Expected: each pledge bears its share (95e18); actual: B can never be paid and burns at deadline+3d.","severity":"low","snippet":"        p.token.safeTransfer(p.beneficiary, m.amount);","title":"Tranches of the same token share one vault balance and are paid at fixed amounts; a token whose vault balance later falls lets the first-kept pledge take another pledge's tokens"},{"citation":"resolved","description":"Merged from audit_economics, audit_math and audit_flow (same root cause). createPledge only bounds the deadline from below. requestCheck (295), submitVerdict (325) and settleMissed (353) compute `m.deadline + GRACE` with both operands uint64 under checked arithmetic, so for deadline > 2^64-1-259200 they panic 0x11 at every timestamp. The milestone never settles, p.settled never reaches p.count, reclaimBudget reverts NotSettled forever: the tranche and the whole budget, including IMD third parties added with addBudget, are locked. Self-inflicted by the creator except for addBudget donors and the beneficiary. Fix: reject `m.deadline > type(uint64).max - GRACE` (or a sane MAX_HORIZON) in createPledge, or compare in uint256.","line":238,"path":"src/PledgeVault.sol","reproduction":"Traced against the code: oracle configured; createPledge(KEPT, ben, 'x', [{check:'shipped', deadline: type(uint64).max, amount: 100e18}], 1e18) succeeds and pulls 100e18 KEPT + 1e18 IMD. ben.requestCheck(id,0) reverts Panic(0x11) at line 295. vm.warp(type(uint64).max); settleMissed(id,0) reverts Panic(0x11) at line 353. reclaimBudget(id) reverts NotSettled. Expected: creation rejected, or settleable after grace; actual: 100e18 KEPT and 1e18 IMD unrecoverable.","severity":"low","snippet":"            if (m.deadline <= block.timestamp) revert DeadlinePassed();","title":"Milestone deadline has no upper bound; deadline > type(uint64).max - 3 days makes `m.deadline + GRACE` overflow on every path, locking the tranche and the pledge's IMD budget forever (merged: economic"},{"citation":"resolved","description":"From audit_math. requestCheck and submitVerdict (line 325) share the cutoff deadline + GRACE, and the price is paid to the intake immediately (line 306). A check opened at or just before the cutoff can only be answered in the same block; afterwards submitVerdict reverts GraceOver and settleMissed burns the tranche and zeroes openCheckPrice, so the beneficiary loses up to 0.5 IMD per attempt plus a tranche the panel would have certified. Fix: require a response window before the cutoff for new checks, or accept verdicts for checks opened in time for a window after.","line":295,"path":"src/PledgeVault.sol","reproduction":"Traced: pledge with deadline dl, budget 1e18, price 0.5e18. vm.warp(dl + 3 days): requestCheck(id,0) succeeds, intake +0.5e18. vm.warp(dl + 3 days + 12): submitVerdict(id,0,1,true,exp,validSig) reverts GraceOver (line 325). settleMissed(id,0) burns the tranche. Expected: a sold check can be answered; actual: fee lost and tranche burned.","severity":"low","snippet":"        if (block.timestamp > m.deadline + GRACE) revert GraceOver();","title":"requestCheck sells a check up to the last second of grace, when no verdict can land; the fee goes to the intake and is never refunded"},{"citation":"resolved","description":"From audit_economics. Each kept verdict refunds the full paid price from the shared fund into the pledge's budget, which the creator reclaims. createPledge accepts any ERC-20 and a tranche of 1 wei, and an honest panel must answer 'kept' to a trivially true check. One actor can cycle 0.5 IMD of working capital through up to 8 milestones per pledge, recovering it each time, and move all donations to the intake at gas cost only. No profit to the actor unless intake-aligned; donors and real teams who find the fund empty lose. Low. Fix preserving design: cap refunds per pledge/creator, or require a minimum tranche or lock duration for refunds.","line":342,"path":"src/PledgeVault.sol","reproduction":"Traced: fund=RefereeFund(IMD, vault) set, donate 4e18 IMD. Team deploys a junk ERC20, createPledge(junk, team, 'drain', 8 milestones {check:'Is 1 + 1 = 2?', deadline T0+1+i, amount 1}, 0.5e18). For i in 0..7: requestCheck(id,i) (0.5e18 to intake), panel signs kept, submitVerdict(id,i,1,true,exp,sig) refunds 0.5e18 into budget. reclaimBudget(id) returns 0.5e18. End: team IMD unchanged, fund.balance()==0, intake +4e18.","severity":"low","snippet":"            p.budget += refunded;","title":"Referee Fund can be emptied into the intake by zero-value pledges with trivially true checks: refunds have no per-pledge cap and any token qualifies"},{"citation":"resolved","description":"Merged from three reports. Application-contract constructor arguments may only be static words, $token, $requester or $contract:EarlierName. `$owner` (README.md:14 and :64) is not one, so a launch.json written from the README is refused or the owner word is replaced by hand without a reviewed source. The owner holds every privileged power of the vault (panel, quorum, intake, price, Referee Fund). Documentation/manifest defect; no contract change. Fix: README should say `$requester`; PledgeVault args [\"$requester\", \"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"], RefereeFund args [\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\", \"$contract:PledgeVault\"].","line":64,"path":"README.md","reproduction":"Input: launch.json contracts[0] = {\"contract\":\"PledgeVault\",\"constructorArgs\":[\"$owner\",\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]} as README directs. Expected: owner() == economics.remainderTo. Actual: `$owner` is outside the placeholder set ($token/$requester/$contract:Name), so the manifest is unresolvable.","severity":"low","snippet":"- The vault's owner is the launch's owner (`owner_`, filled by the manifest with `$owner`). It is","title":"README tells the manifest step to fill PledgeVault's owner_ with `$owner`, which the launch manifest does not define; it must be `$requester` (merged: permissions, economics, flow)"},{"citation":"resolved","description":"From write_foundry_tests (reported low; recalibrated to info). If burn(amount) succeeds but burns less than amount, `burned` is false and stranded[token] grows by the full amount, overstating the tokens actually held. stranded is a reporting counter only; no transfer reads it and no pledge's funds are affected. KEPT burns exactly. Fix: add only `token.balanceOf(this) - (before - amount)` style remaining delta, i.e. the amount not burned.","line":525,"path":"src/PledgeVault.sol","reproduction":"Reproduced: forge test --match-path test/scratch/Proof_55556ffcb80e.t.sol fails 'stranded must count tokens actually left in custody: 150 != 200'. Token whose burn(a) burns a/2; two milestones of 100; settleMissed on the first after grace: vault holds 150, pending 100, stranded 100 (expected 50).","severity":"info","snippet":"        if (!burned) stranded[address(token)] += amount;","title":"_burnTranche records the full amount as stranded when a token's burn removes only part of it"},{"citation":"resolved","description":"Not a defect; intended owner/panel powers recorded with preconditions. (1) Pledges may be created while intake==0; the first configuration applies at once, so the panel judging already-locked tokens is chosen with no notice (narrowing: apply instantly only while pledgeCount==0). (2) renounceOwnership is inherited from Ownable unchanged; if called before configuration, requestCheck reverts OracleNotConfigured forever and every tranche burns via settleMissed. (3) An open check has no timeout: a silent panel blocks attempt 2 (CheckAlreadyOpen) and the tranche burns with the fee kept. (4) The owner may be sole signer, quorum 1 and intake.","line":388,"path":"src/PledgeVault.sol","reproduction":"Fresh vault: team createPledge(...); owner scheduleOracleConfig(x, 0.5e18, [s], 1) -> pendingReadyAt == block.timestamp; applyOracleConfig() same block. Or owner renounceOwnership() while intake==0 -> requestCheck reverts OracleNotConfigured; at deadline+3d+1 settleMissed burns.","severity":"info","snippet":"        uint64 readyAt = intake == address(0) ? uint64(block.timestamp) : uint64(block.timestamp) + NOTICE;","title":"Owner trust assumptions: first oracle configuration applies with no notice to already-locked pledges, renounceOwnership is one step, an unanswered open check blocks retries (merged: permissions, econo"}],"hash":"229bf921a739ba0cf7c642c3a3d5e0db9fd11c5a8934fe321b5db370e15863f3","nodeId":"c6a97b36-fa81-4bbf-b915-f3d8954d32f7","outcome":"completed","summary":"I finished the review and wrote 8 findings, plus a coverage row for each of the 21 entry points, to `.imd-findings.json`. Nothing else in the tree changed: I ran the specialists' proofs from `test/scratch/` and then deleted that folder.\n\n**Kept findings, after merging duplicates and adjusting severity:**\n\n1. **Medium – Referee Fund refunds are trusted without checking what arrived** (`src/PledgeVault.sol:341`). This merges four reports. `setRefereeFund` checks the fund's `vault()` but not which token it pays out, and it can only be called once. `submitVerdict` then adds whatever number `refund()` returns to the pledge's budget without checking that any IMD came in. The result is budgets with no IMD behind them, paid out of other pledges' IMD. It also breaks the stated promise that \"the owner never touches locked tokens or budgets\": the owner could install a fake fund to drain budgets, or one that reverts so tranches certified as kept burn instead. The attached proof (`Proof_25bf54b8863d`) fails on the current code as claimed and passes with either fix.\n2. **Low – Pledges in the same token share one vault balance** (`:337`). If a token's balance can shrink after deposit, the first pledge to be kept is paid in full and later ones can never be paid. The proof reproduced this. I lowered it from medium because KEPT and other plain tokens are unaffected, and the docs already say to use plain tokens only.\n3. **Low – Deadline overflow** (`:238`). This merges three reports. A deadline within 3 days of `type(uint64).max` makes `m.deadline + GRACE` overflow on every later path. That locks the tranche and the IMD budget forever, including IMD other people added with `addBudget`.\n4. **Low – Checks are sold up to the last second of the grace period** (`:295`). A check opened at the cutoff can't be answered, so the fee is lost and the tranche burns.\n5. **Low – The Referee Fund can be drained into the intake address** (`:342`). Pledges of worthless tokens with obviously true checks can recycle 0.5 IMD of working capital until the fund is empty, at gas cost only.\n6. **Low – Wrong placeholder in the README** (`README.md:64`). This merges three reports. The README says to fill the vault's owner with `$owner`, which the launch manifest doesn't define; it should say `$requester`.\n7. **Info – The `stranded` counter is overstated after a partial burn.** It is only a counter, and no funds are affected.\n8. **Info – Owner trust assumptions**, merged from three reports: the first oracle setup takes effect immediately, `renounceOwnership` is one step, and a check the panel never answers blocks any retry.\n\nI dropped nothing as unreproducible: every specialist finding held up, either through its proof or by tracing it through the code. Findings 3–5 were confirmed by tracing the code, not by running tests.\n\n**My own pass:** The KEPT token is plain OpenZeppelin code, with the fixed supply minted once to the deployer. All three contracts are well under the deployment size limits (PledgeVault is 15,468 bytes deployed). I found nothing the specialists missed.\n\n**Static-analysis leads:** None of them held up:\n- the \"uninitialized\" `_milestones` is filled by `push`;\n- the strict-equality checks are intended;\n- reentrancy is blocked by `nonReentrant`.\n\n**Coverage:** 13 rows hold and 8 point to a finding.","treeHash":null,"usage":{"cachedInputTokens":480956,"inputTokens":17,"model":"claude-opus-5-5","outputTokens":14638,"runtime":"claude","turns":10,"wallClockMs":172786}},{"artifacts":[],"attempt":1,"bundleHash":"e3c869132a7e72ae09f8597406dbbd54bf7ccade8076ee17e43e14632fdc57ad","device":"234a4a00ca903cb9","findings":[],"hash":"3d5aeb2b781bbbe6394a7336dd7dbf1ae57b9b5d2286a1b4ff9324dab759400a","nodeId":"6143d305-6bc1-438a-bad5-1fa113775fad","outcome":"completed","summary":"I fixed the medium finding and three of the low and info findings. I disputed the other four with reasons. All eight answers are in `.imd-responses.json`. `forge build`, `forge test` (60 passed, 0 failed, run with the verifier's fuzz seed) and `forge fmt --check` all pass. The required proof, `Proof_3faae70e1fe9`, now passes against the changed code. Nothing is committed.\n\n**Fixed:**\n- **Refund credits IMD that never arrived (medium, `3faae70e…`):**\n  - `setRefereeFund` now also refuses a fund that holds a different token from the vault's IMD (new error `FundTokenMismatch`).\n  - When a verdict is \"kept\", the vault now credits only the IMD that actually arrived, measured by its own balance and capped at the price paid. It no longer trusts the number the fund reports back.\n  - A fund whose refund fails now credits nothing and no longer blocks the release of a kept tranche.\n  - New tests cover a fund holding another token, a fake fund that reports a refund without paying, and a fund whose refund always fails.\n- **Deadline overflow (`6e35872a…`):** `createPledge` now rejects a deadline too close to the largest possible timestamp (`DeadlineTooFar`), so every milestone can still be settled after its grace period. I had the limit one second off at first; a new test caught it and I corrected it.\n- **Stranded count after a partial burn (`f3885963…`):** `stranded[token]` now counts only the part of a tranche the burn did not remove, not the full amount.\n- **Rebasing and decaying tokens (`9f3ceb7f…`):** I fixed the comment in `KeptToken.sol` that wrongly claimed the vault refuses rebasing or decaying tokens. The README now warns that a token whose balances can fall later may leave the last pledges of that token unpayable. I did not change the vault's accounting; KEPT and ordinary ERC-20s are unaffected.\n\n**Disputed, no code change:**\n- **Late check requests (`5313b7ce…`):** asking and answering sharing the deadline-plus-three-days cutoff follows the site's rule. The README now tells teams to request checks early.\n- **Referee Fund drained by trivial pledges (`d7c72df6…`):** uncapped refunds are the stated design, and a cap would be a rule the brief doesn't give. The README now tells donors refunds are uncapped.\n- **`$owner` in the README (`61dc8c9c…`):** the launch guidance for this task explicitly says to fill the owner argument with `$owner`. `$requester` stands for the address that receives the remaining 20%, which isn't necessarily the owner. I left the README as it was.\n- **Owner trust assumptions (`a1d9d608…`):** the report itself says this is intended owner behaviour, not a defect. I added one point to the README: an open check that the panel never answers has no timeout.\n\nThe working tree also shows untracked entries I didn't create, such as `.bashrc`, `.gitmodules`, `.mcp.json` and items under `.claude/`. They are links to `/dev/null` put there by this machine's sandbox, not real files, so I left them alone.","treeHash":"2bdf26e56e526fbc31ceb0ddda491d621ba961a6","usage":{"cachedInputTokens":744868,"inputTokens":32,"model":"claude-opus-5-5","outputTokens":11805,"runtime":"claude","turns":21,"wallClockMs":170277}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"bc1e515ca5c78ac4","findings":[{"citation":"resolved","description":"Boundary x invariant. The vault is meant to keep vault.balanceOf(token) >= the sum of pending m.amount for that token. KeptToken.sol:13 says the vault 'refuses tokens with taxes, rebasing or balance decay', but _pullExact only checks that one transferFrom delivered the exact amount. A token whose transfers are exact but whose holder balances can later fall (negative rebase, demurrage, an admin burn/clawback) gets through. All pledges of the same token share one vault balance, and tranches are paid as fixed m.amount (line 337 `p.token.safeTransfer(p.beneficiary, m.amount);`). So whichever pledge is kept first takes its full amount and the whole shortfall falls on the pledges kept later. Those pledges can never be paid: every kept verdict reverts, the milestone ends up burned or stranded via settleMissed, and stranded[token] counts tokens that do not exist. A positive rebase does the reverse and leaves the surplus stuck in the vault for good. Fix: keep a per-token total of locked tranches and pay each pledge pro rata to balanceOf / lockedTotal, or keep the vault's 'plain token only' claim to what it actually checks and say in the docs that rebasing tokens break it.","line":497,"path":"src/PledgeVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PledgeVault} from \"src/PledgeVault.sol\";\n\ncontract PlainToken is ERC20 {\n    constructor() ERC20(\"P\", \"P\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\n/// Transfers are exact, but the owner of the token can shrink any balance (negative rebase / decay).\ncontract DecayToken is ERC20 {\n    constructor() ERC20(\"D\", \"D\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n    function decay(address who, uint256 a) external { _burn(who, a); }\n}\n\ncontract MathScratch is Test {\n    PlainToken imd;\n    PledgeVault vault;\n    address owner = makeAddr(\"owner\");\n    address team = makeAddr(\"team\");\n    address ben = makeAddr(\"ben\");\n    address intake = makeAddr(\"intake\");\n    uint256 k1;\n    address s1;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        imd = new PlainToken();\n        vault = new PledgeVault(owner, address(imd));\n        (s1, k1) = makeAddrAndKey(\"s1\");\n        address[] memory p = new address[](1);\n        p[0] = s1;\n        vm.prank(owner);\n        vault.scheduleOracleConfig(intake, 0.5e18, p, 1);\n        vault.applyOracleConfig();\n        imd.mint(team, 10e18);\n        vm.prank(team);\n        imd.approve(address(vault), type(uint256).max);\n    }\n\n    function _one(address token, uint64 deadline, uint256 amount, uint256 budget) internal returns (uint256) {\n        PledgeVault.MilestoneInput[] memory ms = new PledgeVault.MilestoneInput[](1);\n        ms[0] = PledgeVault.MilestoneInput(\"shipped?\", deadline, amount);\n        vm.prank(team);\n        return vault.createPledge(token, ben, \"x\", ms, budget);\n    }\n\n    function _sig(uint256 id, uint256 m, uint8 a, bool kept, uint256 exp) internal view returns (bytes[] memory s) {\n        (uint8 v, bytes32 r, bytes32 ss) = vm.sign(k1, vault.verdictDigest(id, m, a, kept, exp));\n        s = new bytes[](1);\n        s[0] = abi.encodePacked(r, ss, v);\n    }\n\n    // Finding: the plain-token check is a one-time snapshot; a shared balance that shrinks lets one\n    // pledge's kept tranche consume another pledge's tokens.\n    function test_decayShiftsLossToOtherPledge() public {\n        DecayToken t = new DecayToken();\n        t.mint(team, 200e18);\n        vm.prank(team);\n        t.approve(address(vault), type(uint256).max);\n        uint256 a = _one(address(t), uint64(block.timestamp + 10 days), 100e18, 1e18);\n        uint256 b = _one(address(t), uint64(block.timestamp + 10 days), 100e18, 1e18);\n        t.decay(address(vault), 10e18); // vault now holds 190 for 200 of tranches\n        vm.prank(ben);\n        vault.requestCheck(a, 0);\n        vault.submitVerdict(a, 0, 1, true, block.timestamp + 1 days, _sig(a, 0, 1, true, block.timestamp + 1 days));\n        vm.prank(ben);\n        vault.requestCheck(b, 0);\n        // Expected: pledge B's kept tranche is still payable. Actual: reverts, A took B's tokens.\n        vault.submitVerdict(b, 0, 1, true, block.timestamp + 1 days, _sig(b, 0, 1, true, block.timestamp + 1 days));\n    }\n\n}","reproduction":"DecayToken: an OZ ERC20 with exact transfers plus decay(who,a) => _burn(who,a). Team creates pledge A (1 milestone, 100e18) and pledge B (1 milestone, 100e18): both _pullExact checks pass and the vault holds 200e18. The token decays the vault by 10e18, leaving 190e18. A's beneficiary gets a kept verdict, and submitVerdict(A,0,1,true,...) sends 100e18 (vault now 90e18). B's kept verdict: submitVerdict(B,0,1,true,...) reverts ERC20InsufficientBalance(vault, 90e18, 100e18). Expected: each pledge bears its own share of the loss (95e18 each). Actual: A is paid in full and B can never be paid. Reproduced with forge (test below fails with ERC20InsufficientBalance).","severity":"medium","snippet":"    function _pullExact(IERC20 token, uint256 amount) private {\n        uint256 before = token.balanceOf(address(this));\n        token.safeTransferFrom(msg.sender, address(this), amount);\n        uint256 received = token.balanceOf(address(this)) - before;\n        if (received != amount) revert NotPlainToken(amount, received);","title":"Plain-token check runs once at deposit, so a token whose balance shrinks lets one pledge's release take another pledge's tranche"},{"citation":"resolved","description":"Boundary x precision. createPledge only bounds the deadline from below. Every later path computes `m.deadline + GRACE` in uint64 (lines 295, 325, 353). For deadline > 2^64-1-259200 that sum overflows, so requestCheck, submitVerdict and settleMissed all panic with 0x11 at any timestamp, and the milestone can never settle. Smaller but still absurd deadlines have the same effect without the panic: for example 1_800_000_000_000, a millisecond timestamp sent by a JS frontend, is about year 59,000. Because reclaimBudget needs settled == count (line 276), the pledge's whole IMD budget is locked too, including IMD anyone else added through addBudget, which takes deposits for any pledge with no check. The team chooses the deadline, so the main victims are budget donors, plus a pledge whose 'missed deadline burns' promise can never trigger. Fix: cap deadlines, e.g. require(m.deadline <= block.timestamp + MAX_HORIZON) (say 5 years). That also rules out the overflow.","line":238,"path":"src/PledgeVault.sol","reproduction":"createPledge(token, ben, 'x', [{check:'shipped?', deadline:type(uint64).max, amount:100e18}], 1e18) succeeds. Warp to type(uint64).max + 1 and call settleMissed(id,0): it reverts with panic 0x11 (arithmetic overflow) at `m.deadline + GRACE` (line 353; same at 295 and 325). reclaimBudget(id) reverts NotSettled at every timestamp. Expected: deadline rejected at creation, or settleable after grace. Actual: the 100e18 tranche and the 1e18 IMD budget are locked forever. Reproduced in forge (fails with panic 0x11).","severity":"low","snippet":"            if (m.deadline <= block.timestamp) revert DeadlinePassed();","title":"Deadline has no upper bound; near type(uint64).max, deadline + GRACE overflows and the pledge and every budget added to it are locked forever"},{"citation":"resolved","description":"Boundary x invariant. requestCheck and submitVerdict (line 325) share the same cutoff, deadline + GRACE, and the request pays checkPrice to the intake straight away (line 306). A check opened at or near the cutoff can only be answered in the same block, so the beneficiary pays up to 0.5 IMD per attempt (1.5 IMD over three) for a verdict that cannot be submitted. settleMissed then burns the tranche and zeroes openCheckPrice, so the payment is never refunded even if the answer would have been 'kept'. Fix: stop new checks some response window before the cutoff (e.g. require block.timestamp + RESPONSE_WINDOW <= m.deadline + GRACE), or let a verdict for a check opened in time land within a window after its request.","line":295,"path":"src/PledgeVault.sol","reproduction":"Pledge with deadline dl and budget 1e18, oracle price 0.5e18. Warp to dl + 3 days: requestCheck(id,0) succeeds and intake receives 0.5e18. Warp 12 s (next block) and submitVerdict(id,0,1,true,exp,validSig) reverts GraceOver(). settleMissed then burns the 100e18 tranche. Expected: a check the vault sells can be answered. Actual: 0.5 IMD lost and the tranche burned despite a 'kept' verdict. Reproduced in forge (fails with GraceOver).","severity":"low","snippet":"        if (block.timestamp > m.deadline + GRACE) revert GraceOver();","title":"requestCheck charges checkPrice up to the last second of grace, when no verdict can still land"}],"hash":"40ea4618a01f32d9ad6d904555938aebc163fd90a2a23c1d34c91a65d2892213","nodeId":"0db603e9-0890-4888-9c65-4c27b1da5004","outcome":"completed","summary":"The math review of KeptToken, PledgeVault and RefereeFund is done: three findings, each reproduced with a Foundry test that fails on the current code. I made the three passes (Math Precision, Boundary, Numerical Gap) myself; no helpers were used. The findings and coverage are in `.imd-findings.json`; no source files were changed, and the scratch tests were deleted after running.\n\n**Findings**\n\n1. **Medium – a token whose balance can shrink breaks the per-pledge accounting** (`src/PledgeVault.sol:497`). The \"plain token\" check runs only once, when tokens are deposited. All pledges of one token share the vault's balance, and each tranche is paid out as a fixed amount. If the token later shrinks the vault's balance (a negative rebase, decay or an admin burn), whichever pledge is kept first is paid in full and later pledges can never be paid.\n   - Reproduction: two pledges of 100e18 each, then the vault's balance drops by 10e18. Pledge A's kept verdict pays out 100e18; pledge B's then reverts with `ERC20InsufficientBalance(vault, 90e18, 100e18)`.\n   - This contradicts the comment in `KeptToken.sol:13`, which says the vault refuses tokens with \"rebasing or balance decay\".\n   - A standalone proof test is attached to the finding.\n\n2. **Low – deadlines have no upper limit** (`src/PledgeVault.sol:238`).\n   - With a deadline near the 64-bit maximum, `deadline + GRACE` overflows, so `requestCheck`, `submitVerdict` and `settleMissed` all fail with an overflow error at any time.\n   - Without the overflow, a mistaken deadline such as a millisecond timestamp (around the year 59,000) has the same effect.\n   - Either way the milestone never settles, so `reclaimBudget` is blocked for good, including IMD that third parties added with `addBudget`.\n\n3. **Low – a check can be bought when no answer can arrive** (`src/PledgeVault.sol:295`). Requesting a check and submitting its verdict share the same cutoff (deadline + 3 days). A check requested at the cutoff still costs 0.5 IMD; the verdict submitted one block later reverts with `GraceOver`, and the tranche is then burned with no refund.\n\n**Coverage**\n\nThe record has 23 rows: all 21 entry points plus two invariants (that the vault's IMD covers all pledge budgets, which holds, and that its token balance covers all pending tranches, which is finding 1). None were left unreached.\n\n**Static-analysis leads I checked and dropped**\n- The \"uninitialized\" warnings for `_milestones`, `total`, `lastDeadline` and `last` are false: these variables start at zero by design.\n- The strict equality in `_burnTranche` is intended: it is the exact-burn check.\n- The reentrancy report at line 341 is guarded: the function is `nonReentrant` and the fund is set once and must name this vault.\n- The subtraction `before - amount` in `_burnTranche` can't underflow with a standard `burn`.\n\n**Limits:** I only modelled the shrinking-balance case with a mock token, not a real rebasing token. I didn't look at anything outside the math area in depth.","treeHash":null,"usage":{"cachedInputTokens":519798,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":16035,"runtime":"claude","turns":13,"wallClockMs":174231}},{"artifacts":[],"attempt":1,"bundleHash":"885adda0f3ec585599c2c86b2a1702e5e43af47566ee82b9ab8620716bdeb190","device":"797dadf3f0881394","findings":[],"hash":"4f47d3720ab9a3cd5555e2e0d92c61862761249b8e8eb873ebb373d90c7f2e4c","nodeId":"6143d305-6bc1-438a-bad5-1fa113775fad","outcome":"completed","summary":"The work is complete and committed as `0131a0f`. A fresh clone of that commit builds, passes all tests and the format check offline.\n\n**What was built**\n\nThe brief's site describes KEPT as \"promises the swarm enforces\": a team locks tokens behind milestones, the IMD swarm signs verdicts, kept tranches unlock and missed ones burn. The delivery is three contracts under `src/`:\n\n- **KeptToken** is the token itself: `keptonchain` / `KEPT`, 18 decimals, one billion minted once to the deployer (the factory) in the constructor. It is a plain ERC-20 with holder-only burn and ERC-2612 permit. It has no owner, no mint, no pause and no transfer rule, which is exactly what the site demands of tokens the vault accepts and what the protected floor requires of launch flows.\n- **PledgeVault** locks any plain ERC-20 behind up to eight milestones. The beneficiary requests checks, paying up to 0.5 IMD each to an owner-set intake. Anyone submits the panel's EIP-712 verdict, verified against the signer set and quorum. Kept tranches release to the beneficiary. Milestones not proven within three days of the deadline burn through the token's own burn, or stay stranded forever if the token has none. Oracle changes wait seven days after the first configuration. Nothing can withdraw, pause or upgrade. Owner comes from a constructor argument, never from the deployer.\n- **RefereeFund** holds donated IMD and refunds the check of each kept milestone when it can. Only the vault may draw.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| `forge build` (solc 0.8.26, `bytecode_hash = \"none\"`) | passes |\n| `forge test` with the verifier's fuzz seed | 54 passed, 0 failed |\n| `forge fmt --check` | passes |\n| Scratch replica of the protected floor (supply to deployer, admin-selector probes, bytecode scan for DELEGATECALL/CALLCODE/SELFDESTRUCT on all three contracts) | passes |\n\nDependencies are vendored as plain files: forge-std 1.9.7 and OpenZeppelin 5.1.0, no submodules.\n\n**Documented limits and open items**\n\n- The site's \"1% of every KEPT/IMD trade feeds the Referee Fund\" needs a Uniswap v4 dynamic-fee hook. This launch deploys no hook and uses the fixed 1.25% policy fee, so the fund is donation-funded. The README states this and notes a later hook launch can route fees in.\n- The IMD swarm's intake address, check price, signer panel and quorum are values only the oracle operator holds. They are owner-set after launch through a timelocked setter and documented under \"After launch\". Check requests revert as not configured until then. Pledge creation works before configuration so KEPT's own roadmap can be locked on day one.\n- Slither was not available on this machine. The code avoids the six rejected detector patterns by construction.\n- Tests passing are not an audit. The vault holds other people's tokens and needs independent adversarial review before anything of value is locked.","treeHash":"a5e6020c21172852e5f7e9575d6dd1a88b870497","usage":{"cachedInputTokens":1693894,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":60446,"runtime":"claude","turns":39,"wallClockMs":752507}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0e3b71e2ffcd200b","findings":[],"hash":"5e66e8c1a19209efce09f9487ed8565f9186f3d89b486c6165a55bba4dea970c","nodeId":"c6a97b36-fa81-4bbf-b915-f3d8954d32f7","outcome":"completed","summary":"All earlier findings are settled and no new defect reproduces. The findings file holds an empty list and 24 coverage rows, one for each of the 21 entry points plus three invariants.\n\n**Settled findings**\n\n- **Medium, refund credited from the fund's return value.** Fixed. The vault now requires the fund's token to match at `src/PledgeVault.sol:440` and credits only the measured IMD delta, capped at the price, inside try/catch at lines 346 to 351. My proof now passes, and the author's three new tests pass.\n- **Low, deadline overflow.** Fixed at `src/PledgeVault.sol:242`. The boundary test rejects max minus grace and settles max minus grace minus one at the last uint64 timestamp.\n- **Low, decaying tokens share one balance.** Settled by documentation, which was one of the two fixes I offered. The code is unchanged, so the old proof still fails, but the misleading claim in KeptToken.sol is gone and the README states the risk.\n- **Low, check sold at the end of grace.** Disputed by the author as a product rule and documented. I accept that and drop it.\n- **Low, fund cycled to the intake.** Disputed as the site's stated design and documented for donors. Dropped.\n- **Low, README names `$owner`.** The manifest now exists and uses `$requester`, which deploys correctly. README lines 14 and 66 still say `$owner`, a stale wording only. Dropped from findings and noted in coverage.\n- **Info, partial burn overstates stranded.** Fixed. Stranded now grows by the unburned remainder only.\n- **Info, owner trust assumptions.** Documented in the README as agreed. No change needed.\n\n**Verification run**\n\n- Full project suite: 106 tests pass, including the two invariant suites.\n- Changed lines read as new code. The try/catch cannot be reentered because of nonReentrant, the balance snapshot follows the tranche release of a non-IMD token, and a fund that lies or reverts credits nothing.\n- Manifest checked against the schema and the constructors. Runtime and initcode sizes are well under the CREATE2 limits. The IMD address returns symbol IMD with 18 decimals and has code on mainnet.\n\nThe scratch copies of the proofs were removed, so the tree holds only the findings file.","treeHash":null,"usage":{"cachedInputTokens":483880,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":12906,"runtime":"claude","turns":19,"wallClockMs":297939}},{"artifacts":[],"attempt":2,"bundleHash":"69243db45cfff07e48c91a2ecbbea0eeed0f29a85aa4d7054350f8d9197185eb","device":"f009de0ce52c7b3a","findings":[],"hash":"9ad8484a66bc484295a7539d399514bbf512f26c76ae0475be2df03030ead441","nodeId":"216962a2-0d9b-420d-98d0-eee83aea5b52","outcome":"completed","summary":"I added regression tests for the implementation's latest fixes. The full suite passes with the verifier's seed: `forge build` succeeds and `forge test` shows 106 passed, 0 failed (98 tests that were already there plus 8 new ones).\n\n**What changed in the implementation:** the author fixed both defects I reported last round. A Referee Fund in the wrong currency could create IMD credits that nothing backed, and a partial burn overstated how many tokens were stranded. The author also capped how far out a milestone deadline can be, and added tests of their own for these. The earlier rejection (\"fetch failed\") was an infrastructure error, so nothing in my earlier tests needed to change.\n\n**What I added:**\n- **`test/PledgeRefundAndBurnRegressions.t.sol`** (new, 8 tests, fuzz runs set to 1000 in the file). It tests the fixes the way an attacker would:\n  - **Real fund with too little money:** with any fund balance from zero to twice the check price, the pledge is credited exactly the smaller of the balance and the price. The vault's IMD always matches the budget, and the creator gets back exactly that.\n  - **Fund that pays too much:** the credit stops at the price paid. The surplus stays in the vault and nobody can claim it.\n  - **Fund that tries to call back into the vault during a refund:** the refund is undone and credits nothing, and the beneficiary still gets their tokens. I checked that this test would fail if the vault's reentrancy guard were missing.\n  - **Who can set the fund:** an address that isn't a fund is refused, and so is anyone but the owner.\n  - **Partial burns:** for a token that burns any share of a missed tranche (0% to 100%), exactly the unburned rest is counted as stranded. It never cuts into a second pledge of the same token, which is still paid in full.\n  - **Deadlines near the uint64 maximum:** every deadline the vault accepts can still be settled once the grace period ends. If a later milestone's deadline is too far out, the whole pledge is rolled back and every token returned.\n- **`test/PROPERTIES.md`:** the \"Limits and findings\" section described the two defects as still open. It now says they are fixed and lists what the new file covers.\n\n**Findings:** none outstanding, so there is no `.imd-findings.json`. I also checked whether someone submitting a verdict could starve the refund call of gas to deny the team its refund. It doesn't work: any gas limit low enough to make the refund fail leaves too little for the rest of the transaction, so the whole thing reverts.\n\n**Not committed:** git has no author identity set on this machine, so the commit failed. I didn't change the git config. The two files are staged and ready for the harness's own commit.","treeHash":"7e56dbb077c05d08d1c30b6c0515fde61e284b6b","usage":{"cachedInputTokens":453726,"inputTokens":23,"model":"claude-opus-5-5","outputTokens":13323,"runtime":"claude","turns":12,"wallClockMs":348953}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9812c5faf226f7c7","findings":[],"hash":"aafb844c0043b1e7e7a3b6e8f75b04b4ca55279da22c6ec667591e3f74eccf00","nodeId":"216962a2-0d9b-420d-98d0-eee83aea5b52","outcome":"failed","summary":"fetch failed","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":10021}},{"artifacts":[],"attempt":1,"bundleHash":"c11d3664620026823bb2cd3946fe3d2b12e7debdeec4affe20c2eeb43a274e09","device":"f2eb60f00abb5b80","findings":[{"description":"setRefereeFund validates only fund.vault(), not fund.imd(), and can only be called once. A genuine RefereeFund constructed with this vault but another ERC-20 is accepted. submitVerdict then trusts the numeric refund return value and credits it as IMD even though the fund transferred the other token. Reclaiming that budget either reverts or spends IMD backing other pledges. This requires an owner configuration mistake, not an unprivileged change of the configured fund. Validate the fund currency before attaching it and do not credit IMD that was not received.","line":429,"path":"src/PledgeVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeptToken} from \"src/KeptToken.sol\";\nimport {PledgeVault} from \"src/PledgeVault.sol\";\nimport {RefereeFund} from \"src/RefereeFund.sol\";\n\ncontract WrongFundCurrencyProof is Test {\n    function test_wrongCurrencyFundMustNotCreateUnbackedBudget() public {\n        vm.warp(1_800_000_000);\n        KeptToken imd = new KeptToken();\n        KeptToken wrongCurrency = new KeptToken();\n        KeptToken collateral = new KeptToken();\n        PledgeVault vault = new PledgeVault(address(this), address(imd));\n        RefereeFund fund = new RefereeFund(address(wrongCurrency), address(vault));\n\n        // A fixed setter may reject this configuration. That prevents the defect.\n        try vault.setRefereeFund(address(fund)) {} catch { return; }\n\n        (address panelMember, uint256 panelKey) = makeAddrAndKey(\"proof-panel\");\n        address[] memory panel = new address[](1);\n        panel[0] = panelMember;\n        vault.scheduleOracleConfig(makeAddr(\"proof-intake\"), 0.5e18, panel, 1);\n        vault.applyOracleConfig();\n        wrongCurrency.transfer(address(fund), 0.5e18);\n        collateral.approve(address(vault), 1e18);\n        imd.approve(address(vault), 0.5e18);\n        PledgeVault.MilestoneInput[] memory ms = new PledgeVault.MilestoneInput[](1);\n        ms[0] = PledgeVault.MilestoneInput(\"Ship\", uint64(block.timestamp + 1 days), 1e18);\n        uint256 id = vault.createPledge(address(collateral), address(this), \"wrong refund asset\", ms, 0.5e18);\n        vault.requestCheck(id, 0);\n        uint256 expiry = block.timestamp + 1 hours;\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(panelKey, vault.verdictDigest(id, 0, 1, true, expiry));\n        bytes[] memory sigs = new bytes[](1);\n        sigs[0] = abi.encodePacked(r, s, v);\n        vault.submitVerdict(id, 0, 1, true, expiry, sigs);\n\n        assertGe(imd.balanceOf(address(vault)), vault.getPledge(id).budget, \"refund credited a different currency as IMD\");\n        vault.reclaimBudget(id);\n    }\n}","reproduction":"Deploy separate KeptToken instances for IMD, collateral and the wrong currency. Deploy PledgeVault(owner, IMD), then RefereeFund(wrongCurrency, vault). The owner successfully attaches this fund. Configure a 0.5e18 check price and a one-signer panel, donate 0.5e18 wrongCurrency to the fund, and create a one-milestone pledge with 1e18 collateral and 0.5e18 IMD budget. Request the check and submit a valid kept verdict. Expected: the mismatched fund is rejected, or any recorded budget is backed by actual IMD. Actual: vault.getPledge(1).budget is 500000000000000000 while IMD.balanceOf(vault) is 0. The scratch proof failed with \"refund credited a different currency as IMD: 0 < 500000000000000000\" using forge test --match-path test/scratch/WrongFundCurrencyProof.t.sol. The proof also attempts reclaimBudget after the solvency assertion.","severity":"medium","title":"A RefereeFund using a different currency creates unbacked IMD budgets"},{"description":"The vault accepts plain ERC-20 transfers and treats the burn extension as optional. If burn(amount) succeeds but burns only part of the tranche, its effects remain committed, yet _burnTranche adds the full requested amount to stranded. That mapping is documented as tokens still held forever, so it becomes larger than the actual leftover custody. KEPT itself burns the full amount and is not affected. This finding concerns supported external tokens whose optional burn does not remove the exact requested amount; it does not demonstrate theft of another pledge. Account for the remaining amount or isolate and roll back an inexact burn before recording the full tranche as stranded.","line":525,"path":"src/PledgeVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {KeptToken} from \"src/KeptToken.sol\";\nimport {PledgeVault} from \"src/PledgeVault.sol\";\n\n// Transfers are ordinary, exact ERC-20 transfers. Only the optional burn extension differs.\ncontract PartialBurnProofToken is KeptToken {\n    function burn(uint256 amount) public override {\n        super.burn(amount / 2);\n    }\n}\n\ncontract PartialBurnProof is Test {\n    function test_partialBurnMustNotOverstateStrandedTokens() public {\n        vm.warp(1_800_000_000);\n        KeptToken imd = new KeptToken();\n        PartialBurnProofToken asset = new PartialBurnProofToken();\n        PledgeVault vault = new PledgeVault(address(this), address(imd));\n        asset.approve(address(vault), 200);\n        PledgeVault.MilestoneInput[] memory ms = new PledgeVault.MilestoneInput[](2);\n        ms[0] = PledgeVault.MilestoneInput(\"First\", uint64(block.timestamp + 1 days), 100);\n        ms[1] = PledgeVault.MilestoneInput(\"Second\", uint64(block.timestamp + 10 days), 100);\n        uint256 id = vault.createPledge(address(asset), address(this), \"partial burn\", ms, 0);\n        vm.warp(block.timestamp + 4 days + 1);\n        vault.settleMissed(id, 0);\n        uint256 outstanding = vault.getMilestone(id, 1).amount;\n        assertEq(asset.balanceOf(address(vault)), outstanding + vault.stranded(address(asset)), \"stranded must count tokens actually left in custody\");\n    }\n}","reproduction":"Use a KeptToken subclass with otherwise unchanged ERC-20 transfers and burn(amount) implemented as super.burn(amount / 2). Create two milestones of 100 minor units each, with deadlines one and ten days ahead. After four days plus one second, settle the first milestone. Expected: custody equals the 100 still pending plus the recorded stranded amount. Actual: the vault holds 150, the second milestone remains pending for 100, and stranded is 100, so recorded custody is 200. forge test --match-path test/scratch/PartialBurnProof.t.sol failed with \"stranded must count tokens actually left in custody: 150 != 200\".","severity":"low","title":"A partial optional burn overstates stranded token accounting"}],"hash":"ab68a0b939aed021ffa7aa6c57e957404276a87318a43ff504fda964964bf32e","nodeId":"216962a2-0d9b-420d-98d0-eee83aea5b52","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests for KEPT, PledgeVault, and RefereeFund.\n\n`forge build` and `forge test` pass with the supplied seed: **93 tests**, including **57,344 randomized handler calls**.\n\nReported two reproduced defects in `.imd-findings.json`, with proofs:\n\n- **Medium:** mismatched fund currency creates unbacked IMD budgets.\n- **Low:** partial burns overstate stranded balances.","treeHash":"35bcd24f57fe84e0d579e7ad6acdb8f85560e0e7","usage":{"cachedInputTokens":1979520,"inputTokens":90348,"model":"gpt-6-astra","outputTokens":34061,"runtime":"codex","turns":8,"wallClockMs":1007437}},{"artifacts":[],"attempt":1,"bundleHash":"73384b412234f826887a4e15812d4ad1cf070d712b52bfa9b0834cd7b4ec214f","device":"3f91b58cf7cd2d45","findings":[],"hash":"c4bd43509e4d2d6cde96e36d89648a38221cbc4dbb93975cdf05f22697f568c6","nodeId":"5b18d856-423c-4c17-bd72-f36ef7e16fb9","outcome":"completed","summary":"The existing launch.json is correct and I left it unchanged. The brief says this is a revision round but lists no findings, so I verified the manifest against the accepted tree instead of rewriting it.\n\n**What I checked**\n\n- **Token section** matches `src/KeptToken.sol`: contract name KeptToken, name keptonchain, symbol KEPT, 18 decimals, no constructor arguments, and totalSupply of one billion times 10^18 minted once to msg.sender.\n- **Application contracts** match their constructors in declaration order. PledgeVault takes the requester as owner and the IMD address. RefereeFund takes the IMD address and the earlier PledgeVault via the contract placeholder.\n- **Pool and economics** are byte for byte what the launch reference gives: IMD as the paired currency, fee 12500, tick spacing 60, the one to one provenance price, and the requester's verbatim economics object.\n- **Shape** uses only schema keys, lowercase addresses, string uint256 values, numeric fee, tick spacing and decimals, and notes well under the 4,000 character limit.\n- **Owners protected test** is satisfied: PledgeVault's owner is the requester, not the factory or IMD, and RefereeFund has no owner.\n\n**Verification**\n\n- The build succeeds, and the accepted suite passes with the verifier's fuzz seed.\n- A scratch test in test/scratch parses the manifest, deploys all three contracts in manifest order as the factory with the resolved arguments, and confirms the supply, decimals, name, symbol, owner and wiring. It passes.\n\n| Suite | Result |\n| --- | --- |\n| Accepted tests | 59 passed, 0 failed |\n| Scratch manifest test | 1 passed |\n\nOne note outside my scope: README.md still describes the vault's owner argument as `$owner`, which is not a manifest placeholder. The manifest correctly uses `$requester`. I did not touch the README since only launch.json is writable in this task.","treeHash":"9ba8e2b7fff37224f06551491dea8bf86ecb475a","usage":{"cachedInputTokens":216315,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":5243,"runtime":"claude","turns":7,"wallClockMs":81402}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dd3018ab6b18e7bc","findings":[{"citation":"resolved","description":"Area: Access Control / Trust Gap / Asymmetry. Every IMD inflow a user triggers (createPledge, addBudget) goes through _pullExact (src/PledgeVault.sol:497-502), which measures the vault's balance before and after and reverts unless exactly `amount` arrived. The one inflow the owner's configuration controls, the Referee Fund refund in submitVerdict (src/PledgeVault.sol:339-345), is credited to `p.budget` from the value `fund.refund(paid)` returns, with no balance measurement. The only guard on which fund may be installed is `RefereeFund(fund).vault() != address(this)` (src/PledgeVault.sol:429); the fund's `imd()` is never compared with the vault's `imd`, and `refereeFund` can be set only once (line 428), so a mismatch is permanent. The vault's invariant `sum(p.budget) == imd.balanceOf(vault)` therefore depends on the fund paying real IMD, which nothing in the vault enforces. Two preconditions reach the break: (a) honest misconfiguration: the project's own unmodified RefereeFund deployed with a different `imd_` than the vault (any token, or the right token on the wrong chain's address) passes setRefereeFund, and every kept verdict thereafter credits IMD the vault never received; (b) a malicious or compromised owner installs a contract whose `vault()` returns the vault and whose `refund()` returns any number, draining all IMD budgets in one reclaim. Case (b) contradicts the README's explicit guarantee 'The owner cannot touch locked tokens or budgets' (README.md:59-60), which is why this is reported rather than filed as an accepted owner power. In both cases the victim is every other pledge creator: the last creators to call reclaimBudget revert on insufficient IMD and their budgets are stuck. Fix (preserves design): in setRefereeFund also `if (address(RefereeFund(fund).imd()) != address(imd)) revert FundVaultMismatch();`, and in submitVerdict credit the measured delta: `uint256 before = imd.balanceOf(address(this)); fund.refund(paid); uint256 refunded = imd.balanceOf(address(this)) - before;` (optionally cap at `paid`). Either alone closes the honest-misconfiguration path; the delta measurement also closes the malicious-fund path.","line":341,"path":"src/PledgeVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {KeptToken} from \"src/KeptToken.sol\";\nimport {PledgeVault} from \"src/PledgeVault.sol\";\nimport {RefereeFund} from \"src/RefereeFund.sol\";\n\ncontract PlainToken is ERC20 {\n    constructor(string memory n, string memory s) ERC20(n, s) {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice The vault credits `fund.refund(paid)`'s return value to a pledge budget without measuring the IMD\n/// that actually arrived, and `setRefereeFund` accepts any fund whose `vault()` is this vault regardless of\n/// which token the fund holds. An unmodified RefereeFund built with a different `imd_` therefore pays the\n/// vault a worthless token while the vault credits real IMD; the refunded pledge then reclaims IMD that\n/// belongs to another pledge's budget, and that other pledge can no longer reclaim in full.\n/// Passes once the vault either refuses a fund whose `imd()` differs from its own, or credits only the\n/// IMD balance delta the refund produced.\ncontract RefundCreditedByReturnValueTest is Test {\n    uint256 constant PRICE = 0.5e18;\n    uint256 constant T0 = 1_800_000_000;\n\n    KeptToken kept;\n    PlainToken imd;\n    PlainToken other;\n    PledgeVault vault;\n    RefereeFund fund;\n\n    address owner = makeAddr(\"owner\");\n    address teamA = makeAddr(\"teamA\");\n    address teamB = makeAddr(\"teamB\");\n    address intake = makeAddr(\"intake\");\n    address signer;\n    uint256 signerKey;\n\n    function setUp() public {\n        vm.warp(T0);\n        kept = new KeptToken();\n        imd = new PlainToken(\"IMD\", \"IMD\");\n        other = new PlainToken(\"Other\", \"OTH\");\n        vault = new PledgeVault(owner, address(imd));\n        // The project's own RefereeFund, pointed at this vault but holding a different token.\n        fund = new RefereeFund(address(other), address(vault));\n\n        (signer, signerKey) = makeAddrAndKey(\"panel\");\n        address[] memory panel = new address[](1);\n        panel[0] = signer;\n        vm.startPrank(owner);\n        vault.scheduleOracleConfig(intake, PRICE, panel, 1);\n        vault.applyOracleConfig();\n        // Accepted on the current code: only vault() is checked, never imd(). A fixed vault may refuse it.\n        try vault.setRefereeFund(address(fund)) {} catch {}\n        vm.stopPrank();\n\n        other.mint(address(this), 10e18);\n        other.approve(address(fund), 10e18);\n        fund.donate(10e18);\n\n        kept.transfer(teamA, 1_000e18);\n        kept.transfer(teamB, 1_000e18);\n        imd.mint(teamA, 10e18);\n        imd.mint(teamB, PRICE);\n        vm.startPrank(teamA);\n        kept.approve(address(vault), type(uint256).max);\n        imd.approve(address(vault), type(uint256).max);\n        vm.stopPrank();\n        vm.startPrank(teamB);\n        kept.approve(address(vault), type(uint256).max);\n        imd.approve(address(vault), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _one(uint256 amount) internal view returns (PledgeVault.MilestoneInput[] memory ms) {\n        ms = new PledgeVault.MilestoneInput[](1);\n        ms[0] = PledgeVault.MilestoneInput({check: \"shipped\", deadline: uint64(block.timestamp + 30 days), amount: amount});\n    }\n\n    function test_keptVerdictCreditsImdTheVaultNeverReceived() public {\n        // Team A locks a budget of 10 IMD. Team B locks 0.5 IMD, exactly one check.\n        vm.prank(teamA);\n        uint256 a = vault.createPledge(address(kept), teamA, \"A\", _one(1_000e18), 10e18);\n        vm.prank(teamB);\n        uint256 b = vault.createPledge(address(kept), teamB, \"B\", _one(1e18), PRICE);\n        assertEq(imd.balanceOf(address(vault)), 10e18 + PRICE);\n\n        // B asks for a check (0.5 IMD to the intake) and the panel finds the milestone kept.\n        vm.prank(teamB);\n        vault.requestCheck(b, 0);\n        uint256 expiry = block.timestamp + 1 hours;\n        bytes32 digest = vault.verdictDigest(b, 0, 1, true, expiry);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerKey, digest);\n        bytes[] memory sigs = new bytes[](1);\n        sigs[0] = abi.encodePacked(r, s, v);\n        vault.submitVerdict(b, 0, 1, true, expiry, sigs);\n\n        // No IMD arrived with the refund (the fund holds OTH), so the vault's IMD is exactly A's budget.\n        assertEq(imd.balanceOf(address(vault)), 10e18, \"no IMD arrived with the refund\");\n\n        // Expected: B's budget stays 0 because the vault received no IMD. Actual: credited 0.5 IMD.\n        uint256 bBudget = vault.getPledge(b).budget;\n        assertEq(bBudget, 0, \"budget credited with IMD the vault never received\");\n\n        // Expected: A reclaims its whole 10 IMD once its milestone settles.\n        if (bBudget > 0) vault.reclaimBudget(b);\n        vm.warp(T0 + 30 days + 3 days + 1);\n        vault.settleMissed(a, 0);\n        try vault.reclaimBudget(a) {} catch {}\n        assertEq(imd.balanceOf(teamA), 10e18, \"team A did not get its whole budget back\");\n        assertEq(vault.getPledge(a).budget, 0, \"team A's budget is stuck in the vault\");\n    }\n}","reproduction":"State: vault = PledgeVault(owner, IMD). fund = RefereeFund(OTHER, vault) where OTHER is any ERC-20 that is not IMD. owner: scheduleOracleConfig(intake, 0.5e18, [signer], 1); applyOracleConfig(); setRefereeFund(fund) -> succeeds (only vault() is checked). Anyone donates 10e18 OTHER to fund. Team A: createPledge(KEPT, A, \"A\", [1 milestone, 1000e18, deadline +30d], budget 10e18 IMD). Team B: createPledge(KEPT, B, \"B\", [1 milestone, 1e18], budget 0.5e18 IMD). Vault IMD = 10.5e18. B: requestCheck(b, 0) -> 0.5e18 IMD to intake, vault IMD = 10e18. Panel signs Verdict(b,0,1,true,expiry); anyone: submitVerdict(b,0,1,true,expiry,[sig]). Expected: B's budget stays 0 (the vault received no IMD; it received 0.5e18 OTHER). Actual: fund.refund(0.5e18) transfers 0.5e18 OTHER and returns 0.5e18; p.budget[b] = 0.5e18 while vault IMD is still 10e18. Then reclaimBudget(b) pays B 0.5e18 of A's IMD; after A's milestone settles, reclaimBudget(a) reverts (vault holds 9.5e18 IMD against A's 10e18 budget) and A's budget is stuck. Malicious variant: owner installs a contract with vault()==vault and refund(x) returning imd.balanceOf(vault) without transferring; one self-pledge with a kept verdict (owner's own panel, quorum 1) followed by reclaimBudget drains every pledge's IMD. Scratch test test/scratch/RefundCreditedByReturnValue.t.sol fails on this code: 'budget credited with IMD the vault never received: 500000000000000000 != 0'.","severity":"medium","snippet":"            uint256 refunded = fund.refund(paid);\n            p.budget += refunded;","title":"Kept-verdict refund is credited from the fund's return value, not from IMD actually received; setRefereeFund never checks the fund's token, so a fund holding another token lets one pledge reclaim othe"},{"citation":"resolved","description":"Area: Access Control, deployment and initialization (who holds each role once deployment ends). PledgeVault's constructor takes `address owner_` and hands it the only privileged role in the system: oracle panel, quorum, intake, check price and the Referee Fund. The README (lines 14 and 64) instructs the manifest step to fill it with `$owner`. The custom-token launch manifest defines only `$token`, `$requester` and `$contract:EarlierName` for application-contract address arguments (and `$factory`, `$poolManager`, `$launchNumber` for the token). `$owner` is not one of them, so a launch.json copied from the README is refused, or the manifest writer substitutes something else without a reviewed source of truth for who owns the vault. The intended owner is the requester (economics.remainderTo), which is exactly what `$requester` resolves to. Since `Ownable(owner_)` reverts on address(0), a wrong placeholder cannot silently produce an ownerless vault, but a literal address pasted in its place is the unauthorized-owner case the manifest review is told to look for. Fix: README lines 14 and 64 should say `$requester`, and the manifest step should write `\"constructorArgs\": [\"$requester\", \"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]` for PledgeVault and `[\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\", \"$contract:PledgeVault\"]` for RefereeFund.","line":64,"path":"README.md","reproduction":"Input: a launch.json with contracts[0] = {\"contract\":\"PledgeVault\",\"constructorArgs\":[\"$owner\",\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]} as the README directs. Expected: the deployer resolves the first word to the requester's address and PledgeVault.owner() equals economics.remainderTo. Actual: `$owner` is not a defined placeholder for application contracts (only $token, $requester, $contract:Name are), so the manifest is refused or the word is replaced by hand. The code itself is unaffected; no launch.json exists in the tree yet, so this is a documentation/manifest defect, not a contract defect.","severity":"low","snippet":"- The vault's owner is the launch's owner (`owner_`, filled by the manifest with `$owner`). It is","title":"README names a constructor placeholder `$owner` that the launch manifest does not define; the vault's owner argument must be written as `$requester` or a literal address"},{"citation":"resolved","description":"Not a defect: these are the documented powers of the intentional owner role, recorded here with actor and preconditions as the assignment asks, so the judge can weigh them against the README's claims. (1) Pledges may be created before any oracle exists (README.md:27-28), and the first scheduleOracleConfig sets readyAt = now, so the panel that decides those pledges' outcomes is chosen with zero notice to teams who already locked tokens; later rotations wait 7 days and the notice cannot be bypassed because `intake` can never return to address(0) (scheduleOracleConfig rejects intake_ == 0 at line 370). (2) Nothing stops the owner from naming itself as the only signer with quorum 1 and as intake, so the owner can decide every verdict (release to the beneficiary or let the tranche burn) and collect every check fee; it still cannot redirect a tranche to itself. (3) Ownable2Step does not override renounceOwnership (lib/openzeppelin-contracts/contracts/access/Ownable.sol:76), so the owner can leave the vault with no owner in one call; if that happens before the first configuration, no check can ever run and every pending tranche burns at deadline + 3 days via the permissionless settleMissed; if it happens after a panel key is compromised, the panel can never be rotated. All three are consistent with README.md:62-74, which asks for the owner key to be held in a multisig. No change to the design is proposed; the first-config-instant rule could be narrowed (apply at once only while pledgeCount == 0) if the author wants pledgers who locked before configuration to get the same 7-day notice as everyone else.","line":388,"path":"src/PledgeVault.sol","reproduction":"Owner-only sequences, listed so the preconditions are explicit. (1) team: createPledge(...) while intake == 0; owner: scheduleOracleConfig(ownerAddr, 0.5e18, [ownerAddr], 1) -> pendingReadyAt == block.timestamp; anyone: applyOracleConfig() in the same block -> owner is sole signer and intake. (2) beneficiary: requestCheck -> 0.5 IMD to owner; owner signs kept=false three times -> tranche burns at deadline + 3 days. (3) owner: renounceOwnership() before any configuration -> requestCheck reverts OracleNotConfigured forever; settleMissed(id, m) after each deadline + 3 days burns every tranche. Expected per README: these are accepted trust assumptions; reported for the record, not as a defect.","severity":"info","snippet":"        uint64 readyAt = intake == address(0) ? uint64(block.timestamp) : uint64(block.timestamp) + NOTICE;","title":"Trust assumptions of the owner role: first oracle configuration applies with no notice to pledges already locked, the owner may be panel, quorum and intake at once, and renounceOwnership is one step"}],"hash":"d3101d2b2b310d4c2dfd5e70c0fedcfed1547e71acd309d5d5c4f47f5f0d20ad","nodeId":"6d2ae2c7-3c74-4459-a0b1-177efdebd2fd","outcome":"completed","summary":"The review is complete. The findings file is written, validated against the tree, and the single proof test fails on the current code and passes under either fix.\n\n**Area covered:** permissions, using the Access Control, Trust Gap and Asymmetry guides against KeptToken, PledgeVault and RefereeFund. I mapped every role and guard, diffed the paired inflow/outflow paths for IMD budgets and tranches, and traced each of the 21 listed entry points. All 54 existing tests pass.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Medium, with proof.** The vault credits a kept-verdict refund from whatever the Referee Fund's `refund` returns, never from IMD it measured arriving, and `setRefereeFund` checks only the fund's `vault()`, not its `imd()`. The project's own unmodified RefereeFund deployed with a different token passes the one-shot setter, and every kept verdict thereafter credits a budget with IMD the vault never received. That pledge then reclaims IMD belonging to other pledges, and the last creators to reclaim revert with their budgets stuck. A malicious owner can use the same gap to drain all budgets, which contradicts the README's guarantee that the owner cannot touch budgets. Every user-driven inflow uses the exact-arrival check in `_pullExact`; this is the one inflow that does not. The scratch test at `test/scratch/RefundCreditedByReturnValue.t.sol` fails here and passes under either a token check in the setter or a balance-delta credit.\n2. **Low.** The README tells the manifest step to fill the vault's owner with `$owner`, a placeholder the launch manifest does not define. The intended value is `$requester`.\n3. **Info, trust assumptions.** The first oracle configuration applies with no notice to pledges already locked, the owner may be sole signer, quorum and intake at once, and renounce is one step. Documented in the README, recorded with concrete sequences, not reported as defects.\n\n**Coverage:** 24 rows, one for each listed entry point plus three invariants. Three entry points and one invariant point at finding 1. Everything else holds, including the EIP-712 replay surface and the 7-day notice, which cannot be shortened because the intake address can never return to zero.\n\n**Not reached in depth:** economics-only angles outside my area, such as anyone converting Referee Fund donations into intake revenue at zero net cost through trivially kept pledges. I left it for the economics specialist since it is not a permission defect.","treeHash":null,"usage":{"cachedInputTokens":970028,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":36957,"runtime":"claude","turns":30,"wallClockMs":554352}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"771f83f312eace21","findings":[{"citation":"resolved","description":"createPledge only requires each deadline to be in the future and ascending. Every later path that settles a milestone computes `m.deadline + GRACE` in checked uint64 arithmetic: requestCheck (line 295: `if (block.timestamp > m.deadline + GRACE) revert GraceOver();`) and settleMissed (line 353: `if (block.timestamp <= m.deadline + GRACE) revert GraceNotOver();`). A deadline greater than type(uint64).max - 3 days makes both revert with an arithmetic panic for the life of the contract. The milestone can never be checked, proven or burned, `p.settled` never reaches `p.count`, so reclaimBudget reverts with NotSettled forever. The locked tranche and the whole IMD budget (the creator's and anything third parties addBudget) are stuck in the vault, contradicting the invariant that every tranche ends released or burned and every budget is reclaimable. Invariant guide: 'abuse boundaries'. The creator chooses the deadline, so the direct loss is self-inflicted, but a beneficiary that differs from the creator and any addBudget donor are shorted, and the pledge reads as a live commitment that can never resolve. Fix: in createPledge reject `m.deadline > type(uint64).max - GRACE` (or compute the grace end in uint256).","line":238,"path":"src/PledgeVault.sol","reproduction":"Oracle configured (any panel). team: createPledge(kept, beneficiary, \"x\", [{check:\"shipped?\", deadline: type(uint64).max, amount: 100e18}], budget 1e18) succeeds and pulls 100e18 KEPT + 1e18 IMD. beneficiary: requestCheck(id, 0) reverts with Panic(0x11) at line 295 instead of opening a check. vm.warp(type(uint64).max); settleMissed(id, 0) reverts with Panic(0x11) at line 353 instead of burning. reclaimBudget(id) reverts NotSettled. Expected: a pledge that can always be resolved; actual: 100e18 KEPT and 1e18 IMD are unrecoverable. Verified with test/scratch/Economics.t.sol test_deadlineOverflowLocksPledgeForever.","severity":"low","snippet":"            if (m.deadline <= block.timestamp) revert DeadlinePassed();","title":"Milestone deadline is not bounded, so deadline + GRACE can overflow and lock a pledge's tranche and budget forever"},{"citation":"resolved","description":"The vault's IMD accounting invariant is `imd.balanceOf(vault) >= sum of p.budget`. submitVerdict (line 341-342: `uint256 refunded = fund.refund(paid); p.budget += refunded;`) credits whatever RefereeFund.refund returns to the pledge's budget, and RefereeFund.refund transfers `paid` of the fund's own immutable `imd` token (src/RefereeFund.sol:55-61). setRefereeFund validates only that the fund names this vault. A RefereeFund constructed with a different token address (a manifest/constructor mistake, or the wrong launch's fund) is accepted, and every kept verdict then adds up to 0.5e18 to a budget while the vault receives 0.5e18 of the other token. Budgets exceed the vault's IMD, so reclaimBudget pays the first creators out of later pledges' IMD and the last reclaim reverts with ERC20InsufficientBalance: a third party loses budget they deposited. This is a one-time owner configuration, so it is a validation gap rather than an attack, but the mismatch is permanent (FundAlreadySet) and the check is one line. Fix: also require `RefereeFund(fund).imd() == imd`.","line":429,"path":"src/PledgeVault.sol","reproduction":"fund = new RefereeFund(OTHER_TOKEN, vault); owner: setRefereeFund(fund) succeeds. donate 10e18 OTHER to fund. team: createPledge(kept, beneficiary, \"a\", 1 milestone 1e18, budget 1e18); team2: createPledge(kept, beneficiary, \"b\", 1 milestone 1e18, budget 1e18); vault IMD = 2e18. beneficiary: requestCheck(a, 0) sends 0.5e18 IMD to intake (vault IMD 1.5e18, budget a = 0.5e18). submitVerdict(a, 0, 1, true, expiry, panelSig): budget a becomes 1e18 while vault IMD stays 1.5e18 and vault now holds 0.5e18 OTHER. reclaimBudget(a) pays 1e18 IMD; vault IMD = 0.5e18 against b's budget 1e18. After b's milestone burns (settleMissed at deadline+3d+1), reclaimBudget(b) reverts ERC20InsufficientBalance. Expected: team2 recovers its 1e18 IMD; actual: 0.5e18 of it is gone. Verified with test/scratch/Economics.t.sol test_fundOverOtherTokenShortsOtherPledges.","severity":"low","snippet":"        if (RefereeFund(fund).vault() != address(this)) revert FundVaultMismatch();","title":"setRefereeFund checks fund.vault() but not fund.imd(), so a fund over another token inflates budgets with no IMD backing and shorts other pledges' reclaims"},{"citation":"resolved","description":"The Referee Fund's purpose is to make keeping a real promise free. The refund is unconditional on what was pledged: createPledge accepts any ERC-20 the caller deploys, a tranche of 1 wei, and a check text the honest panel must answer yes to ('Is 1 + 1 = 2?'). Each kept verdict refunds the full check price from the shared fund into the caller's budget, which the caller reclaims. Economic Security guide: 'legitimate features turned against the protocol' / 'cheapest griefing vector'. Who profits: the intake (receives every check fee the fund repays). Who loses: donors and the real teams that find the fund empty when their kept milestones are refunded. Cost to the actor: gas and 0.5 IMD of working capital per open check, fully recovered; eight milestones per pledge move 4 IMD per cycle. The actor does not gain, so this is griefing or a way for an intake-aligned party to convert donations into fees, and it depends on the panel answering trivially-true checks; that limits severity to low. Fixes that preserve the design: refund only pledges whose token is KEPT, require a minimum tranche value or minimum lock duration, or cap refunds per pledge or per creator.","line":341,"path":"src/PledgeVault.sol","reproduction":"RefereeFund(imd, vault) set; donate 4e18 IMD. team deploys junk ERC-20, mints 8 wei, createPledge(junk, team, \"drain\", 8 milestones each {check:\"Is 1 + 1 = 2?\", deadline T0+1+i, amount 1}, budget 0.5e18). For i in 0..7: team requestCheck(id, i) pays 0.5e18 to intake; panel signs kept; submitVerdict(id, i, 1, true, expiry, sig) refunds 0.5e18 from the fund into the budget. reclaimBudget(id) returns 0.5e18 to team. End state: team IMD unchanged, fund.balance() == 0, intake holds 4e18. Expected: donations subsidise real pledges; actual: emptied by one zero-value pledge in one block. Verified with test/scratch/Economics.t.sol test_fundDrainedToIntakeAtZeroNetCost.","severity":"low","snippet":"            uint256 refunded = fund.refund(paid);","title":"Referee Fund can be routed to the intake by anyone at zero net cost: no minimum stake, no per-pledge refund cap, any token qualifies"},{"citation":"resolved","description":"Outside the economics area but launch-blocking if followed. The custom-token-launch rules allow application-contract constructor arguments to be static words, `$token`, `$requester` (economics.remainderTo) or `$contract:EarlierName`; no `$owner` exists. README.md line 14 repeats it in the contracts table. A manifest written from the README would fail admission, or if `$owner` were taken literally the constructor would receive an unresolved value. Every privileged power of the vault (oracle panel, intake, check price, Referee Fund) hangs on this one address, so it must resolve to the requester. Fix: document `$requester` (or a static lowercase address the requester controls) for `owner_`.","line":64,"path":"README.md","reproduction":"Write launch.json contracts[0] = {\"contract\":\"PledgeVault\",\"constructorArgs\":[\"$owner\",\"0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7\"]} as the README instructs. Expected: a manifest the deployer resolves; actual: `$owner` is not among $token / $requester / $contract:Name, so the manifest is refused or unresolvable. With \"$requester\" in its place the same manifest describes the intended owner.","severity":"low","snippet":"- The vault's owner is the launch's owner (`owner_`, filled by the manifest with `$owner`). It is","title":"README directs the manifest to fill PledgeVault's owner with `$owner`, a placeholder the launch manifest does not define"},{"citation":"resolved","description":"Not a defect; privileged powers the design asks for, recorded with their concrete preconditions as the assignment requires. (a) createPledge works while intake == 0, so KEPT can be locked on day one, yet the first scheduleOracleConfig applies with readyAt = now: the panel that will judge already-locked tokens is chosen with no notice, and since there is no exit the seven-day notice for later changes protects no funds either; it is information only. (b) If the owner never configures the oracle, or calls renounceOwnership (inherited from Ownable, not overridden) before configuring, requestCheck reverts OracleNotConfigured forever and every milestone burns at deadline + 3 days via settleMissed; budgets are then reclaimable. (c) An open check has no expiry: if the panel never answers attempt 1, the beneficiary cannot open attempt 2 (CheckAlreadyOpen) and the tranche burns after grace with the fee kept by the intake. (d) The panel the owner configures can sign any verdict, so it can release any tranche to its beneficiary; the Referee Fund's donations are also exposed to that panel. Verdicts are only as honest as the panel, as the README says. These are documented in the README except (a)'s inconsistency with 'there is nothing to protect yet' and (c).","line":388,"path":"src/PledgeVault.sol","reproduction":"State: fresh vault, intake == 0. team: createPledge(kept, beneficiary, ..., deadline D). owner: scheduleOracleConfig(x, 0.5e18, [anySigner], 1) -> pendingReadyAt == block.timestamp; anyone: applyOracleConfig() in the same block. Alternatively owner never configures: at D + 3 days + 1 anyone calls settleMissed and the tranche burns. For (c): beneficiary requestCheck(id, 0), panel silent, beneficiary requestCheck(id, 0) again reverts CheckAlreadyOpen; at D + 3 days + 1 settleMissed burns.","severity":"info","snippet":"        uint64 readyAt = intake == address(0) ? uint64(block.timestamp) : uint64(block.timestamp) + NOTICE;","title":"Trust assumptions to document: tokens can be locked before any oracle exists, the first configuration applies instantly, an open check has no timeout, and an unconfigured or renounced owner burns ever"}],"hash":"db4b36dc4d57ac7de12d9b0b52aecd39253eae6aa2a1c6ebf5ec242a21311a8e","nodeId":"958ac8a8-704f-40af-bb94-2aec700d6834","outcome":"completed","summary":"The review is complete. The findings file is valid JSON, present in the repository root, and the only files I created are the gitignored findings file and the scratch test, so no tracked file changed.\n\n**Outcome.** The economics of the KEPT launch hold up well. The token is a plain OpenZeppelin ERC-20 with burn and permit, and the vault's token and IMD accounting are conservation-safe on every normal path. I found no critical or high defect, so no finding carries a proof file. I reported four low findings and one informational trust-assumption record, each reproduced in a scratch test that passes as written against the current code.\n\n**Findings, in order:**\n- **Unbounded milestone deadline.** A deadline above `type(uint64).max - 3 days` makes the grace arithmetic overflow in both `requestCheck` and `settleMissed`, so the tranche can never be released or burned and the budget never reclaimed. Fix is one bound in `createPledge`.\n- **Referee Fund token not validated.** `setRefereeFund` checks that the fund names the vault but not that it pays the same IMD token. A mismatched fund credits budgets the vault has no IMD for, and a later pledge's reclaim reverts with insufficient balance.\n- **Referee Fund drainable at zero net cost.** Anyone can lock 1 wei of a self-deployed token behind trivially true checks and cycle 0.5 IMD per check from the fund to the intake, emptying donations meant for real teams. The actor does not profit, so this is low, but the design has no minimum stake or refund cap.\n- **README names a `$owner` placeholder** for the vault's owner argument. The manifest rules only define `$token`, `$requester` and `$contract:Name`, so a manifest written from the README would not resolve. This is outside my area but launch-blocking if followed.\n- **Trust assumptions recorded as info:** tokens can be locked before any oracle exists and the first panel applies instantly, an open check has no timeout, and a never-configured or renounced owner means every pledge burns.\n\n**Coverage.** All 21 verifier entry points have a row, plus four invariant rows. The three guides were each applied as a full pass. Static-analysis leads were all checked: the reentrancy and uninitialized-state lines are false positives, since every mutator is `nonReentrant` and the milestone array is populated in `createPledge`. IMD on mainnet was confirmed as an 18-decimal token, so the 0.5e18 price cap is consistent. The full test suite and the format check both pass.","treeHash":null,"usage":{"cachedInputTokens":888952,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":36227,"runtime":"claude","turns":27,"wallClockMs":502823}}],"verification":[{"checks":[{"durationMs":2089,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.98s\nCompiler run successful!\n","passed":true},{"durationMs":145,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122273, ~: 122698)\nLogs:\n  Bound result 2592286111\n  Bound result 1692117632\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99518, ~: 99867)\nLogs:\n  Bound result 2\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 7.34ms (13.86ms CPU time)\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179658, ~: 181883)\nLogs:\n  Bound result 4857\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.08ms (8.20ms CPU time)\n\nRan 36 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1943030, ~: 1940049)\nLogs:\n  Bound result 854624162401449531778630\n  Bound result 35374925733204071710\n\n[PASS] test_addBudgetByAnyone() (gas: 511115)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341659)\n[PASS] test_constructorRejectsZeroImd() (gas: 6105)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 663969)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021154)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292465)\n[PASS] test_createPledgeValidation() (gas: 1214660)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 350808)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601387)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1609308)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1179713)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441557)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398051)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1024909)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125592)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417323)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1454998)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1451953)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1492919)\n[PASS] test_requestCheckGuards() (gas: 1219384)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055260)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903379)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 416870)\n[PASS] test_rescheduleReplacesPending() (gas: 501189)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 192476)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736258)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164057)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2157608)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127097)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1108833)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1244948)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584005)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212027)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 45.63ms (68.26ms CPU time)\n\nRan 3 test suites in 46.57ms (61.05ms CPU time): 54 tests passed, 0 failed, 0 skipped (54 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":141,\"foundry.toml\":27,\"launch.json\":39,\"src/KeptToken.sol\":27,\"src/PledgeVault.sol\":528,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/PledgeVault.t.sol\":840,\"test/RefereeFund.t.sol\":78,\"test/mocks/Mocks.sol\":42},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1d3984f151406964a727f5a67764141f5d1beeb85fd9bde09e001954ea14fdb1","verifiedTreeHash":"38a70bfb488d09270d2134a3ec3088c71ab35a0c","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":2567,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.45s\nCompiler run successful!\n","passed":true},{"durationMs":165,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122301, ~: 122698)\nLogs:\n  Bound result 4857\n  Bound result 2837\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99356, ~: 99867)\nLogs:\n  Bound result 241\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 6.97ms (14.66ms CPU time)\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179515, ~: 181871)\nLogs:\n  Bound result 4857\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.06ms (7.29ms CPU time)\n\nRan 41 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1945569, ~: 1943812)\nLogs:\n  Bound result 854624162401449531778630\n  Bound result 35374925733204071710\n\n[PASS] test_addBudgetByAnyone() (gas: 511462)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341939)\n[PASS] test_constructorRejectsZeroImd() (gas: 6083)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 664853)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021456)\n[PASS] test_createPledgeRejectsDeadlineThatOverflowsGrace() (gas: 612916)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292767)\n[PASS] test_createPledgeValidation() (gas: 1215007)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 351022)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601300)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1612665)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1180005)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441859)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398029)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1025211)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125570)\n[PASS] test_partialBurnStrandsOnlyWhatRemains() (gas: 1196486)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417905)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1455683)\n[PASS] test_refundCreditsOnlyImdThatArrived() (gas: 1438964)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1454930)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1493221)\n[PASS] test_requestCheckGuards() (gas: 1219686)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055607)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903659)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 417217)\n[PASS] test_rescheduleReplacesPending() (gas: 501234)\n[PASS] test_revertingFundDoesNotBlockKeptRelease() (gas: 1422950)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 193235)\n[PASS] test_setRefereeFundRejectsFundOfAnotherToken() (gas: 581709)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736932)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164451)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2158314)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127399)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1109113)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1245228)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584609)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212329)\nSuite result: ok. 41 passed; 0 failed; 0 skipped; finished in 49.95ms (78.29ms CPU time)\n\nRan 3 test suites in 51.13ms (63.98ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":154,\"foundry.toml\":27,\"src/KeptToken.sol\":28,\"src/PledgeVault.sol\":543,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/PledgeVault.t.sol\":905,\"test/RefereeFund.t.sol\":78,\"test/mocks/Mocks.sol\":74},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":2456,"exitCode":0,"name":"slither","output":"[high/high] uninitialized-state at src/PledgeVault.sol:210: PledgeVault._milestones (src/PledgeVault.sol#112) is never initialized. It is used in:\n[medium/high] incorrect-equality at src/PledgeVault.sol:530: PledgeVault._burnTranche(uint256,uint256,IERC20,uint256) (src/PledgeVault.sol#530-542) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PledgeVault.sol:412: PledgeVault.applyOracleConfig() (src/PledgeVault.sol#412-433) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/PledgeVault.sol:232: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256).total (src/PledgeVault.sol#232) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PledgeVault.sol:520: PledgeVault._verifyPanel(bytes32,bytes[]).last (src/PledgeVault.sol#520) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PledgeVault.sol:233: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256).lastDeadline (src/PledgeVault.sol#233) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PledgeVault.sol:347: PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[]).refunded (src/PledgeVault.sol#347) is a local variable never initialized\n[medium/medium] unused-return at src/PledgeVault.sol:314: PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[]) (src/PledgeVault.sol#314-356) ignores return value by fund.refund(paid) (src/PledgeVault.sol#348-350)\n[low/medium] reentrancy-benign at src/PledgeVault.sol:530: Reentrancy in PledgeVault._burnTranche(uint256,uint256,IERC20,uint256) (src/PledgeVault.sol#530-542):\n[low/medium] timestamp at src/PledgeVault.sol:412: PledgeVault.applyOracleConfig() (src/PledgeVault.sol#412-433) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:404: PledgeVault.cancelOracleConfig() (src/PledgeVault.sol#404-409) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:359: PledgeVault.settleMissed(uint256,uint256) (src/PledgeVault.sol#359-370) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:314: PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[]) (src/PledgeVault.sol#314-356) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:292: PledgeVault.requestCheck(uint256,uint256) (src/PledgeVault.sol#292-310) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:210: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256) (src/PledgeVault.sol#210-265) uses timestamp for comparisons","passed":true},{"durationMs":606,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PledgeVault.sol:346: Reentrancy: State change after external call (4 places)\n[low] centralization-risk at src/PledgeVault.sol:30: Centralization Risk (4 places)\n[low] costly-loop at src/PledgeVault.sol:235: Costly operations inside loop (4 places)\n[low] large-numeric-literal at src/KeptToken.sol:23: Large Numeric Literal\n[low] local-variable-shadowing at src/PledgeVault.sol:99: Local Variable Shadows State Variable (3 places)\n[low] require-revert-in-loop at src/PledgeVault.sol:235: Loop Contains `require`/`revert` (4 places)\n[low] storage-array-length-not-cached at src/PledgeVault.sol:417: Storage Array Length not Cached\n[low] unchecked-return at src/PledgeVault.sol:456: Unchecked Return\n[low] uninitialized-local-variable at src/PledgeVault.sol:235: Uninitialized Local Variable (5 places)","passed":true},{"durationMs":1872,"exitCode":0,"name":"proof 3faae70e1fe9","output":"Compiling 54 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.29s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-c61df5e4/Proof_3faae70e1fe9.t.sol:RefundCreditedByReturnValueTest\n[PASS] test_keptVerdictCreditsImdTheVaultNeverReceived() (gas: 1140283)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.07ms (779.22µs CPU time)\n\nRan 1 test suite in 3.02ms (2.07ms 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":"3d5aeb2b781bbbe6394a7336dd7dbf1ae57b9b5d2286a1b4ff9324dab759400a","verifiedTreeHash":"2bdf26e56e526fbc31ceb0ddda491d621ba961a6","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":2627,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.52s\nCompiler run successful!\n","passed":true},{"durationMs":218,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179658, ~: 181883)\nLogs:\n  Bound result 1157\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.06ms (8.71ms CPU time)\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122273, ~: 122698)\nLogs:\n  Bound result 4857\n  Bound result 2837\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99518, ~: 99867)\nLogs:\n  Bound result 2\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 8.04ms (17.04ms CPU time)\n\nRan 36 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1943030, ~: 1940049)\nLogs:\n  Bound result 863801810672335392639428\n  Bound result 9639964449223809107\n\n[PASS] test_addBudgetByAnyone() (gas: 511115)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341659)\n[PASS] test_constructorRejectsZeroImd() (gas: 6105)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 663969)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021154)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292465)\n[PASS] test_createPledgeValidation() (gas: 1214660)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 350808)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601387)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1609308)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1179713)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441557)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398051)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1024909)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125592)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417323)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1454998)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1451953)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1492919)\n[PASS] test_requestCheckGuards() (gas: 1219384)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055260)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903379)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 416870)\n[PASS] test_rescheduleReplacesPending() (gas: 501189)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 192476)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736258)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164057)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2157608)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127097)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1108833)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1244948)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584005)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212027)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 62.48ms (86.43ms CPU time)\n\nRan 3 test suites in 63.67ms (78.57ms CPU time): 54 tests passed, 0 failed, 0 skipped (54 total tests)\n","passed":true},{"durationMs":80,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":141,\"foundry.toml\":27,\"src/KeptToken.sol\":27,\"src/PledgeVault.sol\":528,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/PledgeVault.t.sol\":840,\"test/RefereeFund.t.sol\":78,\"test/mocks/Mocks.sol\":42},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":2786,"exitCode":0,"name":"slither","output":"[high/high] uninitialized-state at src/PledgeVault.sol:208: PledgeVault._milestones (src/PledgeVault.sol#112) is never initialized. It is used in:\n[medium/high] incorrect-equality at src/PledgeVault.sol:402: PledgeVault.applyOracleConfig() (src/PledgeVault.sol#402-423) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/PledgeVault.sol:519: PledgeVault._burnTranche(uint256,uint256,IERC20,uint256) (src/PledgeVault.sol#519-527) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/PledgeVault.sol:230: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256).total (src/PledgeVault.sol#230) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PledgeVault.sol:509: PledgeVault._verifyPanel(bytes32,bytes[]).last (src/PledgeVault.sol#509) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PledgeVault.sol:231: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256).lastDeadline (src/PledgeVault.sol#231) is a local variable never initialized\n[low/medium] reentrancy-benign at src/PledgeVault.sol:519: Reentrancy in PledgeVault._burnTranche(uint256,uint256,IERC20,uint256) (src/PledgeVault.sol#519-527):\n[low/medium] timestamp at src/PledgeVault.sol:289: PledgeVault.requestCheck(uint256,uint256) (src/PledgeVault.sol#289-307) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:208: PledgeVault.createPledge(address,address,string,PledgeVault.MilestoneInput[],uint256) (src/PledgeVault.sol#208-262) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:394: PledgeVault.cancelOracleConfig() (src/PledgeVault.sol#394-399) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:349: PledgeVault.settleMissed(uint256,uint256) (src/PledgeVault.sol#349-360) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:311: PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[]) (src/PledgeVault.sol#311-346) uses timestamp for comparisons\n[low/medium] timestamp at src/PledgeVault.sol:402: PledgeVault.applyOracleConfig() (src/PledgeVault.sol#402-423) uses timestamp for comparisons","passed":true},{"durationMs":562,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PledgeVault.sol:341: Reentrancy: State change after external call (2 places)\n[low] centralization-risk at src/PledgeVault.sol:30: Centralization Risk (4 places)\n[low] costly-loop at src/PledgeVault.sol:233: Costly operations inside loop (4 places)\n[low] large-numeric-literal at src/KeptToken.sol:22: Large Numeric Literal\n[low] local-variable-shadowing at src/PledgeVault.sol:99: Local Variable Shadows State Variable (3 places)\n[low] require-revert-in-loop at src/PledgeVault.sol:233: Loop Contains `require`/`revert` (4 places)\n[low] storage-array-length-not-cached at src/PledgeVault.sol:407: Storage Array Length not Cached\n[low] unchecked-return at src/PledgeVault.sol:445: Unchecked Return\n[low] uninitialized-local-variable at src/PledgeVault.sol:233: Uninitialized Local Variable (5 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4f47d3720ab9a3cd5555e2e0d92c61862761249b8e8eb873ebb373d90c7f2e4c","verifiedTreeHash":"a5e6020c21172852e5f7e9575d6dd1a88b870497","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":4149,"exitCode":0,"name":"build","output":"Compiling 64 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.05s\nCompiler run successful!\n","passed":true},{"durationMs":20398,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122330, ~: 122698)\nLogs:\n  Bound result 1157\n  Bound result 561\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99560, ~: 99879)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 255.57ms (250.01ms CPU time)\n\nRan 41 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1977814, ~: 1944158)\nLogs:\n  Bound result 432000\n  Bound result 98500000002882309451\n\n[PASS] test_addBudgetByAnyone() (gas: 511462)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341939)\n[PASS] test_constructorRejectsZeroImd() (gas: 6083)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 664853)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021456)\n[PASS] test_createPledgeRejectsDeadlineThatOverflowsGrace() (gas: 612916)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292767)\n[PASS] test_createPledgeValidation() (gas: 1215007)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 351022)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601300)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1612665)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1180005)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441859)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398029)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1025211)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125570)\n[PASS] test_partialBurnStrandsOnlyWhatRemains() (gas: 1196486)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417905)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1455683)\n[PASS] test_refundCreditsOnlyImdThatArrived() (gas: 1438964)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1454930)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1493221)\n[PASS] test_requestCheckGuards() (gas: 1219686)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055607)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903659)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 417217)\n[PASS] test_rescheduleReplacesPending() (gas: 501234)\n[PASS] test_revertingFundDoesNotBlockKeptRelease() (gas: 1422950)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 193235)\n[PASS] test_setRefereeFundRejectsFundOfAnotherToken() (gas: 581709)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736932)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164451)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2158314)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127399)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1109113)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1245228)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584609)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212329)\nSuite result: ok. 41 passed; 0 failed; 0 skipped; finished in 287.55ms (303.62ms CPU time)\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179185, ~: 181871)\nLogs:\n  Bound result 35620055862412107534\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 287.55ms (287.92ms CPU time)\n\nRan 8 tests for test/PledgeRefundAndBurnRegressions.t.sol:PledgeRefundAndBurnRegressionsTest\n[PASS] testFuzz_everyAcceptedDeadlineCanStillSettle(uint64) (runs: 1000, μ: 221319, ~: 200526)\nLogs:\n  Bound result 18446744073709343999\n\n[PASS] testFuzz_partialBurnStrandsExactlyTheUnburnedRemainder(uint256,uint256,uint256) (runs: 1000, μ: 1924854, ~: 1925549)\nLogs:\n  Bound result 615\n  Bound result 763600050\n  Bound result 1000\n\n[PASS] testFuzz_realFundCreditsExactlyWhatItCouldPay(uint256) (runs: 1000, μ: 1119596, ~: 1121421)\nLogs:\n  Bound result 160605216565261474\n\n[PASS] test_fundThatReentersCreditsNothingButReleaseStands() (gas: 1238815)\n[PASS] test_laterTooFarMilestoneRollsBackWholePledge() (gas: 322903)\n[PASS] test_overpayingFundCreditsOnlyThePricePaid() (gas: 1172124)\n[PASS] test_setRefereeFundIsOwnerOnlyEvenForAValidFund() (gas: 43768)\n[PASS] test_setRefereeFundRejectsAccountWithoutFundInterface() (gas: 76589)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 287.65ms (671.04ms CPU time)\n\nRan 3 tests for test/RefereeFundFailurePaths.t.sol:RefereeFundFailurePathsTest\n[PASS] test_directDonationAndRefillAfterExhaustionRemainRefundable() (gas: 442431)\n[PASS] test_failedDonationDoesNotInflateTotalDonated() (gas: 228945)\n[PASS] test_unauthorizedRefundCannotChangeAccountingEvenForZero() (gas: 151676)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 287.70ms (697.76µs CPU time)\n\nRan 13 tests for test/PledgeBoundaryProperties.t.sol:PledgeBoundaryPropertiesTest\n[PASS] testFuzz_multiMilestoneCreationAndPermissionlessReclaimConserveFunds(uint256,uint256,uint256) (runs: 1000, μ: 1346270, ~: 1363369)\nLogs:\n  Bound result 3\n  Bound result 333333333333333333333\n  Bound result 448\n\n[PASS] testFuzz_repeatedKeptLifecyclesReturnOnlyDepositsAndPaidCheckRefunds(uint256,uint256) (runs: 1000, μ: 3819531, ~: 3940611)\nLogs:\n  Bound result 516156452374552858635\n  Bound result 6\n\n[PASS] test_budgetFailureCannotCreateUnbackedCredit() (gas: 488163)\n[PASS] test_creationRollsBackBudgetTransferAndIdWhenTokenPullFails() (gas: 915563)\n[PASS] test_freeChecksDoNotDrainFund() (gas: 947233)\n[PASS] test_lastGraceSecondAllowsCheckAndVerdictButNotMissedSettlement() (gas: 808748)\n[PASS] test_maximumMilestonesAndTextLengthsAreAccepted() (gas: 1752662)\n[PASS] test_negativeVerdictCannotBeReplayedAgainstNextAttempt() (gas: 920511)\n[PASS] test_oneSecondPastGraceRejectsEvenSignedOpenCheckAndBurnsExactlyOnce() (gas: 879765)\n[PASS] test_otherPledgeCannotSubsidizeAnEmptyBudget() (gas: 805649)\n[PASS] test_refundUsesPricePaidBeforeOraclePriceChanges() (gas: 1248210)\n[PASS] test_taxedTokenRejectionAlsoRestoresAlreadyPulledBudget() (gas: 1149704)\n[PASS] test_verdictDomainBindsVaultAndChain() (gas: 809456)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 287.76ms (543.06ms CPU time)\n\nRan 19 tests for test/KeptTokenProperties.t.sol:KeptTokenPropertiesTest\n[PASS] testFuzz_allowanceOneShortCannotTransferOrBurn(uint256) (runs: 1000, μ: 156202, ~: 156411)\nLogs:\n  Bound result 18446744073709551615\n\n[PASS] testFuzz_relayedPermitUsesIndependentDomainAndAcceptsDeadlineEquality(uint256) (runs: 1000, μ: 148642, ~: 148823)\n[PASS] testFuzz_selfTransferIsIdentityButDelegatedSelfTransferSpendsAllowance(uint256) (runs: 1000, μ: 217173, ~: 217426)\nLogs:\n  Bound result 5000000000000000000\n\n[PASS] testFuzz_transferAndBurnShareOneFiniteAllowance(uint256,uint256) (runs: 1000, μ: 225713, ~: 227079)\nLogs:\n  Bound result 730681432945580570900236310\n  Bound result 2\n\n[PASS] test_approveOverwritesAndRevokesAllowance() (gas: 164526)\n[PASS] test_approveZeroSpenderReverts() (gas: 38026)\n[PASS] test_burnZeroOneAndEntireSupply() (gas: 212116)\n[PASS] test_constructorEmitsOnlyMintToActualDeployer() (gas: 36381)\n[PASS] test_failedBurnFromPreservesAllowanceAndSupply() (gas: 108244)\n[PASS] test_failedTransferFromPreservesAllowanceAndBalances() (gas: 234989)\n[PASS] test_infiniteAllowanceSurvivesTransferFromAndBurnFrom() (gas: 187104)\n[PASS] test_maximumTransferAndBurnRevertWithoutOverflow() (gas: 78523)\n[PASS] test_permitBindsSpenderValueNonceAndDeadline() (gas: 275773)\n[PASS] test_permitCannotReplayAfterAllowanceRevocation() (gas: 173676)\n[PASS] test_permitRejectsAnotherContractAndAnotherChain() (gas: 168229)\n[PASS] test_permitRejectsMalleableSignatureAndZeroSpender() (gas: 151088)\n[PASS] test_permitRejectsWrongSignerWithoutConsumingNonce() (gas: 99397)\n[PASS] test_transferToZeroRejectsEvenZeroValue() (gas: 71940)\n[PASS] test_zeroAndOneWeiTransfersAndFullSupplyRoundTrip() (gas: 264086)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 287.90ms (1.10s CPU time)\n\nRan 2 tests for test/invariant/KeptTokenInvariant.t.sol:KeptTokenInvariantTest\n[PASS]\nKeptTokenInvariantTest invariants:\n[PASS] invariant_allowancesAndNoncesMatchAuthorizedActions\n[PASS] invariant_supplyEqualsTrackedBalancesAndAuthorizedBurns\n KeptTokenInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭------------------+------------------------+-------+---------+----------╮\n| Contract         | Selector               | Calls | Reverts | Discards |\n+========================================================================+\n| KeptTokenHandler | approve                | 4218  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | burn                   | 4128  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | burnFrom               | 4133  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | permit                 | 4096  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | rejectOverdraw         | 3996  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | rejectRevokedAllowance | 4039  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | transfer               | 4042  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | transferFrom           | 4116  | 0       | 0        |\n╰------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2060\n  Bound result 27\n  Bound result 11\n  Bound result 4550\n  Bound result 19\n  Bound result 99\n  Bound result 3\n  Bound result 4550\n  Bound result 96006417834186598425692473\n  Bound result 341\n  Bound result 981050661936167620858643711\n  Bound result 117222138868161102676620415\n  Bound result 99\n  Bound result 616531639065508864342265961\n  Bound result 2957397980\n  Bound result 839\n  Bound result 49866101550212067579872002\n  Bound result 1000000000000000000000000000\n  Bound result 352000619987870881148478871\n  Bound result 2324776422\n  Bound result 890586944031600685214413284\n  Bound result 911328752226167472280589827\n  Bound result 10703\n  Bound result 10413\n  Bound result 893002222908694276317601817\n  Bound result 18446744073709551615\n  Bound result 2423\n  Bound result 10779\n  Bound result 13581644323148\n  Bound result 1396\n  Bound result 1099\n  Bound result 554145396\n  Bound result 7339\n  Bound result 778\n  Bound result 2843\n  Bound result 320\n  Bound result 0\n\n[PASS] test_handlerExercisesTransferBurnPermitAndFailures() (gas: 1370989)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.36s (9.36s CPU time)\n\nRan 2 tests for test/invariant/PledgeSystemInvariant.t.sol:PledgeSystemInvariantTest\n[PASS]\nPledgeSystemInvariantTest invariants:\n[PASS] invariant_checkBudgetsAndFundRefundsAreFullyBacked\n[PASS] invariant_lockedTokensAreReleasedBurnedOrStillBacked\n[PASS] invariant_milestonesFollowTheStateMachine\n PledgeSystemInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭---------------------+-------------------------------------+-------+---------+----------╮\n| Contract            | Selector                            | Calls | Reverts | Discards |\n+========================================================================================+\n| PledgeSystemHandler | addBudget                           | 2257  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | advanceTime                         | 2251  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | create                              | 2305  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | donate                              | 2280  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | donateToVault                       | 2249  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | reclaim                             | 2148  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | rejectTerminalOrPrematureSettlement | 2206  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | rejectUnauthorizedActions           | 2205  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | request                             | 2196  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | settle                              | 2204  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | verdict                             | 2275  | 0       | 0        |\n╰---------------------+-------------------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1832\n  Bound result 15299\n  Bound result 9874\n  Bound result 81300\n  Bound result 8\n  Bound result 56751992441787215362\n  Bound result 201223\n  Bound result 50\n  Bound result 17211\n  Bound result 100\n  Bound result 154694\n  Bound result 90000000000000000000\n  Bound result 400000000000000000000\n  Bound result 214223\n  Bound result 48010\n  Bound result 7389\n  Bound result 7\n  Bound result 512\n  Bound result 38400\n  Bound result 732\n  Bound result 172800\n  Bound result 345600\n  Bound result 8403618292183702838\n  Bound result 179200\n  Bound result 99999999999999999999\n  Bound result 45287694387644661314\n  Bound result 1068\n  Bound result 1\n  Bound result 3964\n  Bound result 3\n  Bound result 57591665858688462960691942\n  Bound result 2633752031031179846\n  Bound result 5000000000000000000\n  Bound result 172800\n  Bound result 13659\n  Bound result 89600\n\n[PASS] test_handlerReachesRefundRetryBurnStrandingAndReclaim() (gas: 3803602)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 10\n  Bound result 2000000000000000000\n  Bound result 1\n  Bound result 10\n  Bound result 1000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 345600\n  Bound result 345600\n  Bound result 345600\n  Bound result 345600\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.30s (20.29s CPU time)\n\nRan 9 test suites in 20.30s (31.64s CPU time): 106 tests passed, 0 failed, 0 skipped (106 total tests)\n","passed":true},{"durationMs":51,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":154,\"foundry.toml\":27,\"src/KeptToken.sol\":28,\"src/PledgeVault.sol\":543,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/KeptTokenProperties.t.sol\":301,\"test/PROPERTIES.md\":41,\"test/PledgeBoundaryProperties.t.sol\":334,\"test/PledgeRefundAndBurnRegressions.t.sol\":277,\"test/PledgeVault.t.sol\":905,\"test/RefereeFund.t.sol\":78,\"test/RefereeFundFailurePaths.t.sol\":76,\"test/helpers/PledgeSignatures.sol\":36,\"test/helpers/TokenSignatures.sol\":39,\"test/invariant/KeptTokenInvariant.t.sol\":223,\"test/invariant/PledgeSystemInvariant.t.sol\":491,\"test/mocks/Mocks.sol\":74},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9ad8484a66bc484295a7539d399514bbf512f26c76ae0475be2df03030ead441","verifiedTreeHash":"7e56dbb077c05d08d1c30b6c0515fde61e284b6b","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":3940,"exitCode":0,"name":"build","output":"Compiling 63 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.85s\nCompiler run successful!\n","passed":true},{"durationMs":20625,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/RefereeFundFailurePaths.t.sol:RefereeFundFailurePathsTest\n[PASS] test_directDonationAndRefillAfterExhaustionRemainRefundable() (gas: 442431)\n[PASS] test_failedDonationDoesNotInflateTotalDonated() (gas: 228945)\n[PASS] test_unauthorizedRefundCannotChangeAccountingEvenForZero() (gas: 151676)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 744.02µs (641.72µs CPU time)\n\nRan 36 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1960870, ~: 1940217)\nLogs:\n  Bound result 854624162401449531778630\n  Bound result 35374925733204071710\n\n[PASS] test_addBudgetByAnyone() (gas: 511115)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341659)\n[PASS] test_constructorRejectsZeroImd() (gas: 6105)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 663969)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021154)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292465)\n[PASS] test_createPledgeValidation() (gas: 1214660)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 350808)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601387)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1609308)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1179713)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441557)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398051)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1024909)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125592)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417323)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1454998)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1451953)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1492919)\n[PASS] test_requestCheckGuards() (gas: 1219384)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055260)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903379)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 416870)\n[PASS] test_rescheduleReplacesPending() (gas: 501189)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 192476)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736258)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164057)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2157608)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127097)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1108833)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1244948)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584005)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212027)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 70.63ms (85.88ms CPU time)\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122335, ~: 122706)\nLogs:\n  Bound result 999999999999999999999999998\n  Bound result 192218349975660979321561948\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99361, ~: 99873)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 220.79ms (248.72ms CPU time)\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179434, ~: 181901)\nLogs:\n  Bound result 1157\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 220.86ms (220.86ms CPU time)\n\nRan 19 tests for test/KeptTokenProperties.t.sol:KeptTokenPropertiesTest\n[PASS] testFuzz_allowanceOneShortCannotTransferOrBurn(uint256) (runs: 1000, μ: 156181, ~: 156411)\nLogs:\n  Bound result 6165853113781\n\n[PASS] testFuzz_relayedPermitUsesIndependentDomainAndAcceptsDeadlineEquality(uint256) (runs: 1000, μ: 148663, ~: 148823)\n[PASS] testFuzz_selfTransferIsIdentityButDelegatedSelfTransferSpendsAllowance(uint256) (runs: 1000, μ: 217208, ~: 217426)\nLogs:\n  Bound result 512\n\n[PASS] testFuzz_transferAndBurnShareOneFiniteAllowance(uint256,uint256) (runs: 1000, μ: 225634, ~: 227089)\nLogs:\n  Bound result 35532632074328\n  Bound result 218626\n\n[PASS] test_approveOverwritesAndRevokesAllowance() (gas: 164526)\n[PASS] test_approveZeroSpenderReverts() (gas: 38026)\n[PASS] test_burnZeroOneAndEntireSupply() (gas: 212116)\n[PASS] test_constructorEmitsOnlyMintToActualDeployer() (gas: 36381)\n[PASS] test_failedBurnFromPreservesAllowanceAndSupply() (gas: 108244)\n[PASS] test_failedTransferFromPreservesAllowanceAndBalances() (gas: 234989)\n[PASS] test_infiniteAllowanceSurvivesTransferFromAndBurnFrom() (gas: 187104)\n[PASS] test_maximumTransferAndBurnRevertWithoutOverflow() (gas: 78523)\n[PASS] test_permitBindsSpenderValueNonceAndDeadline() (gas: 275773)\n[PASS] test_permitCannotReplayAfterAllowanceRevocation() (gas: 173676)\n[PASS] test_permitRejectsAnotherContractAndAnotherChain() (gas: 168229)\n[PASS] test_permitRejectsMalleableSignatureAndZeroSpender() (gas: 151088)\n[PASS] test_permitRejectsWrongSignerWithoutConsumingNonce() (gas: 99397)\n[PASS] test_transferToZeroRejectsEvenZeroValue() (gas: 71940)\n[PASS] test_zeroAndOneWeiTransfersAndFullSupplyRoundTrip() (gas: 264086)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 233.88ms (873.04ms CPU time)\n\nRan 13 tests for test/PledgeBoundaryProperties.t.sol:PledgeBoundaryPropertiesTest\n[PASS] testFuzz_multiMilestoneCreationAndPermissionlessReclaimConserveFunds(uint256,uint256,uint256) (runs: 1000, μ: 1331327, ~: 1361225)\nLogs:\n  Bound result 6\n  Bound result 62409041938483718359\n  Bound result 47532967741234849602\n\n[PASS] testFuzz_repeatedKeptLifecyclesReturnOnlyDepositsAndPaidCheckRefunds(uint256,uint256) (runs: 1000, μ: 3793758, ~: 3929470)\nLogs:\n  Bound result 179790126041774271478\n  Bound result 2\n\n[PASS] test_budgetFailureCannotCreateUnbackedCredit() (gas: 487861)\n[PASS] test_creationRollsBackBudgetTransferAndIdWhenTokenPullFails() (gas: 914959)\n[PASS] test_freeChecksDoNotDrainFund() (gas: 946931)\n[PASS] test_lastGraceSecondAllowsCheckAndVerdictButNotMissedSettlement() (gas: 804161)\n[PASS] test_maximumMilestonesAndTextLengthsAreAccepted() (gas: 1750246)\n[PASS] test_negativeVerdictCannotBeReplayedAgainstNextAttempt() (gas: 916226)\n[PASS] test_oneSecondPastGraceRejectsEvenSignedOpenCheckAndBurnsExactlyOnce() (gas: 879371)\n[PASS] test_otherPledgeCannotSubsidizeAnEmptyBudget() (gas: 805045)\n[PASS] test_refundUsesPricePaidBeforeOraclePriceChanges() (gas: 1245925)\n[PASS] test_taxedTokenRejectionAlsoRestoresAlreadyPulledBudget() (gas: 1149402)\n[PASS] test_verdictDomainBindsVaultAndChain() (gas: 805171)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 233.95ms (322.38ms CPU time)\n\nRan 2 tests for test/invariant/KeptTokenInvariant.t.sol:KeptTokenInvariantTest\n[PASS]\nKeptTokenInvariantTest invariants:\n[PASS] invariant_allowancesAndNoncesMatchAuthorizedActions\n[PASS] invariant_supplyEqualsTrackedBalancesAndAuthorizedBurns\n KeptTokenInvariantTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭------------------+------------------------+-------+---------+----------╮\n| Contract         | Selector               | Calls | Reverts | Discards |\n+========================================================================+\n| KeptTokenHandler | approve                | 4218  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | burn                   | 4128  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | burnFrom               | 4133  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | permit                 | 4096  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | rejectOverdraw         | 3996  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | rejectRevokedAllowance | 4039  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | transfer               | 4042  | 0       | 0        |\n|------------------+------------------------+-------+---------+----------|\n| KeptTokenHandler | transferFrom           | 4116  | 0       | 0        |\n╰------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2060\n  Bound result 999999999999999999999999999\n  Bound result 5\n  Bound result 4550\n  Bound result 1\n  Bound result 981050661936167620858643711\n  Bound result 411644505095895323817473295\n  Bound result 4078\n  Bound result 4084\n  Bound result 616531639065508864342265961\n  Bound result 2875074758\n  Bound result 352000619987870881148478871\n  Bound result 890586944031600685214413284\n  Bound result 911328752226167472280589827\n  Bound result 893002222908694276317601817\n  Bound result 7828\n  Bound result 7958\n  Bound result 3813890093\n  Bound result 391\n  Bound result 7\n  Bound result 1531\n  Bound result 28\n  Bound result 2750\n  Bound result 36\n  Bound result 73618629961192942332997069\n  Bound result 99\n  Bound result 28\n  Bound result 31447831685093266781106243\n\n[PASS] test_handlerExercisesTransferBurnPermitAndFailures() (gas: 1370989)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.56s (9.55s CPU time)\n\nRan 2 tests for test/invariant/PledgeSystemInvariant.t.sol:PledgeSystemInvariantTest\n[PASS]\nPledgeSystemInvariantTest invariants:\n[PASS] invariant_checkBudgetsAndFundRefundsAreFullyBacked\n[PASS] invariant_lockedTokensAreReleasedBurnedOrStillBacked\n[PASS] invariant_milestonesFollowTheStateMachine\n PledgeSystemInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭---------------------+-------------------------------------+-------+---------+----------╮\n| Contract            | Selector                            | Calls | Reverts | Discards |\n+========================================================================================+\n| PledgeSystemHandler | addBudget                           | 2257  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | advanceTime                         | 2251  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | create                              | 2305  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | donate                              | 2280  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | donateToVault                       | 2249  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | reclaim                             | 2148  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | rejectTerminalOrPrematureSettlement | 2206  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | rejectUnauthorizedActions           | 2205  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | request                             | 2196  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | settle                              | 2204  | 0       | 0        |\n|---------------------+-------------------------------------+-------+---------+----------|\n| PledgeSystemHandler | verdict                             | 2275  | 0       | 0        |\n╰---------------------+-------------------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000000\n  Bound result 2960\n  Bound result 15134\n  Bound result 9634\n  Bound result 81300\n  Bound result 8\n  Bound result 91859439776255017610\n  Bound result 201223\n  Bound result 50\n  Bound result 17029\n  Bound result 27\n  Bound result 154694\n  Bound result 90000000000000000000\n  Bound result 1000000000000000000000\n  Bound result 214223\n  Bound result 324112\n  Bound result 18337\n  Bound result 10\n  Bound result 64\n  Bound result 25600\n  Bound result 720\n  Bound result 4\n  Bound result 345600\n  Bound result 15120729147696408135\n  Bound result 18\n  Bound result 8140560223744957558\n  Bound result 6287694401538074441\n  Bound result 1788\n  Bound result 3724\n  Bound result 3\n  Bound result 57591665858688412067842942\n  Bound result 0\n  Bound result 6000000000000000000\n  Bound result 294400\n  Bound result 13012\n  Bound result 259200\n\n[PASS] test_handlerReachesRefundRetryBurnStrandingAndReclaim() (gas: 3800610)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1\n  Bound result 2\n  Bound result 10\n  Bound result 2000000000000000000\n  Bound result 1\n  Bound result 10\n  Bound result 1000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 345600\n  Bound result 345600\n  Bound result 345600\n  Bound result 345600\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.51s (20.51s CPU time)\n\nRan 8 test suites in 20.51s (31.05s CPU time): 93 tests passed, 0 failed, 0 skipped (93 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":141,\"foundry.toml\":27,\"src/KeptToken.sol\":27,\"src/PledgeVault.sol\":528,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/KeptTokenProperties.t.sol\":301,\"test/PROPERTIES.md\":39,\"test/PledgeBoundaryProperties.t.sol\":334,\"test/PledgeVault.t.sol\":840,\"test/RefereeFund.t.sol\":78,\"test/RefereeFundFailurePaths.t.sol\":76,\"test/helpers/PledgeSignatures.sol\":36,\"test/helpers/TokenSignatures.sol\":39,\"test/invariant/KeptTokenInvariant.t.sol\":223,\"test/invariant/PledgeSystemInvariant.t.sol\":491,\"test/mocks/Mocks.sol\":42},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ab68a0b939aed021ffa7aa6c57e957404276a87318a43ff504fda964964bf32e","verifiedTreeHash":"35bcd24f57fe84e0d579e7ad6acdb8f85560e0e7","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":2146,"exitCode":0,"name":"build","output":"Compiling 56 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.07s\nCompiler run successful!\n","passed":true},{"durationMs":159,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 12 tests for test/KeptToken.t.sol:KeptTokenTest\n[PASS] testFuzz_burnShrinksSupplyExactly(uint256,uint256) (runs: 256, μ: 122301, ~: 122698)\nLogs:\n  Bound result 999999999999999999999999998\n  Bound result 192218349975660979321561948\n\n[PASS] testFuzz_transfersConserveSupply(address,uint256) (runs: 256, μ: 99356, ~: 99867)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_burnFromNeedsAllowance() (gas: 96668)\n[PASS] test_holderCanBurnOwnTokens() (gas: 115235)\n[PASS] test_metadata() (gas: 40130)\n[PASS] test_mintsWholeSupplyToDeployerOnce() (gas: 33570)\n[PASS] test_noMintFunctionExists() (gas: 87750)\n[PASS] test_permitApprovesAndCannotReplay() (gas: 272793)\n[PASS] test_permitRejectsExpired() (gas: 47213)\n[PASS] test_transferFromNeedsAllowance() (gas: 93137)\n[PASS] test_transferMovesExactAmount() (gas: 161245)\n[PASS] test_transferRevertsBeyondBalance() (gas: 37703)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 6.82ms (14.83ms CPU time)\n\nRan 6 tests for test/RefereeFund.t.sol:RefereeFundTest\n[PASS] testFuzz_refundNeverExceedsRequestOrBalance(uint256,uint256) (runs: 256, μ: 179515, ~: 181871)\nLogs:\n  Bound result 1157\n\n[PASS] test_constructorRejectsZeroAddresses() (gas: 5998)\n[PASS] test_donateMovesImdAndCounts() (gas: 122660)\n[PASS] test_donateRejectsZero() (gas: 32074)\n[PASS] test_onlyVaultRefunds() (gas: 113795)\n[PASS] test_refundPaysWhatItCan() (gas: 236748)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.34ms (8.32ms CPU time)\n\nRan 41 tests for test/PledgeVault.t.sol:PledgeVaultTest\n[PASS] testFuzz_fundsAreConservedThroughALifecycle(uint256,uint256,uint8) (runs: 256, μ: 1945569, ~: 1943812)\nLogs:\n  Bound result 854624162401449531778630\n  Bound result 35374925733204071710\n\n[PASS] test_addBudgetByAnyone() (gas: 511462)\n[PASS] test_brokenVerdictKeepsTrancheLockedAndAllowsRetry() (gas: 1341939)\n[PASS] test_constructorRejectsZeroImd() (gas: 6083)\n[PASS] test_createPledgeLocksTokensAndBudget() (gas: 664853)\n[PASS] test_createPledgeRefusesFeeOnTransferToken() (gas: 1021456)\n[PASS] test_createPledgeRejectsDeadlineThatOverflowsGrace() (gas: 612916)\n[PASS] test_createPledgeRevertsWithoutAllowance() (gas: 292767)\n[PASS] test_createPledgeValidation() (gas: 1215007)\n[PASS] test_createPledgeWorksBeforeOracleIsConfigured() (gas: 351022)\n[PASS] test_firstOracleConfigAppliesAtOnce() (gas: 601300)\n[PASS] test_keptVerdictReleasesTrancheAndRefundsCheck() (gas: 1612665)\n[PASS] test_keptVerdictWithoutFundStillReleases() (gas: 1180005)\n[PASS] test_laterOracleConfigWaitsSevenDays() (gas: 924502)\n[PASS] test_oldPanelCannotSignAfterRotation() (gas: 1441859)\n[PASS] test_onlyOwnerSchedulesOrCancels() (gas: 398029)\n[PASS] test_oracleConfigValidation() (gas: 303781)\n[PASS] test_ownerHasNoWayToMoveLockedTokensOrBudgets() (gas: 1025211)\n[PASS] test_ownerIsTheConstructorArgumentAndTwoStep() (gas: 125570)\n[PASS] test_partialBurnStrandsOnlyWhatRemains() (gas: 1196486)\n[PASS] test_questionIsWrittenByTheVault() (gas: 417905)\n[PASS] test_reclaimBudgetOnlyOnceEverythingIsSettled() (gas: 1455683)\n[PASS] test_refundCreditsOnlyImdThatArrived() (gas: 1438964)\n[PASS] test_refundIsPartialWhenFundIsShort() (gas: 1454930)\n[PASS] test_requestCheckAttemptsAreCapped() (gas: 1493221)\n[PASS] test_requestCheckGuards() (gas: 1219686)\n[PASS] test_requestCheckPaysIntakeFromBudget() (gas: 1055607)\n[PASS] test_requestCheckRefusedAfterGrace() (gas: 903659)\n[PASS] test_requestCheckRevertsUntilOracleConfigured() (gas: 417217)\n[PASS] test_rescheduleReplacesPending() (gas: 501234)\n[PASS] test_revertingFundDoesNotBlockKeptRelease() (gas: 1422950)\n[PASS] test_setRefereeFundOnceAndMatching() (gas: 193235)\n[PASS] test_setRefereeFundRejectsFundOfAnotherToken() (gas: 581709)\n[PASS] test_settleMissedBurnsAfterGrace() (gas: 736932)\n[PASS] test_settleMissedClosesAnOpenCheckWithoutRefund() (gas: 1164451)\n[PASS] test_settleMissedStrandsTokensWithoutARealBurn() (gas: 2158314)\n[PASS] test_verdictAcceptedWithinGraceAfterDeadline() (gas: 1127399)\n[PASS] test_verdictRejectsExpiredOrLate() (gas: 1109113)\n[PASS] test_verdictRejectsForgedOrInsufficientSignatures() (gas: 1245228)\n[PASS] test_verdictRejectsSignaturesOverOtherTerms() (gas: 1584609)\n[PASS] test_verdictRejectsWhenNoCheckIsOpenOrAlreadySettled() (gas: 1212329)\nSuite result: ok. 41 passed; 0 failed; 0 skipped; finished in 51.23ms (79.83ms CPU time)\n\nRan 3 test suites in 52.33ms (65.40ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"KeptToken.approve(address,uint256)\",\"KeptToken.burn(uint256)\",\"KeptToken.burnFrom(address,uint256)\",\"KeptToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"KeptToken.transfer(address,uint256)\",\"KeptToken.transferFrom(address,address,uint256)\",\"PledgeVault.acceptOwnership()\",\"PledgeVault.addBudget(uint256,uint256)\",\"PledgeVault.applyOracleConfig()\",\"PledgeVault.cancelOracleConfig()\",\"PledgeVault.createPledge(address,address,string,(string,uint64,uint256)[],uint256)\",\"PledgeVault.reclaimBudget(uint256)\",\"PledgeVault.renounceOwnership()\",\"PledgeVault.requestCheck(uint256,uint256)\",\"PledgeVault.scheduleOracleConfig(address,uint256,address[],uint8)\",\"PledgeVault.setRefereeFund(address)\",\"PledgeVault.settleMissed(uint256,uint256)\",\"PledgeVault.submitVerdict(uint256,uint256,uint8,bool,uint256,bytes[])\",\"PledgeVault.transferOwnership(address)\",\"RefereeFund.donate(uint256)\",\"RefereeFund.refund(uint256)\"],\"files\":{\".gitignore\":6,\"README.md\":154,\"foundry.toml\":27,\"launch.json\":39,\"src/KeptToken.sol\":28,\"src/PledgeVault.sol\":543,\"src/RefereeFund.sol\":65,\"test/KeptToken.t.sol\":171,\"test/PledgeVault.t.sol\":905,\"test/RefereeFund.t.sol\":78,\"test/mocks/Mocks.sol\":74},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c4bd43509e4d2d6cde96e36d89648a38221cbc4dbb93975cdf05f22697f568c6","verifiedTreeHash":"9ba8e2b7fff37224f06551491dea8bf86ecb475a","verifierVersion":"0.1.0+d372f99e"}]}