{"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":"2f49cf2e-f481-4aec-b90e-d01c17c90bae","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"d2ad8cb98cddea8c69763f043bb92ab6da62fb5fbb190de0b470d6dbde98a0d2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"900327dee14b787aea2624fcc971d2fecb0ee8eef846d180e1840ceec305b332","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f8f153aaba5fe8d7e07bd93bddb44eb15bc4b25e72ee45fe6d379cccb7a3118b","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"160c48cc07282a59327823dd3d1b518b82a788f66d7099012a1a130605308531","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"c0d8ca87ae68a0e18c3de401d9c01297051a3b4a337d044a027cd7ff59f4bfb2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"5a3fd23f7b7aad750719b74546bf5c493ed8a8177a8d7735335b310006004c93","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"2403308e28ad7d7869918a9db1d9bccb5daa5daef917aef29aa1e93ec281e0e3","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"ee3aaab3d873471a95144b4062ecfe7ede318118abd0317c5d8be3907ac04c43","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Title: SitOnHands — voluntary timed IMD lock (no early exit)\n\nOne-liner: Users deposit IMD and choose a lock period; funds are forcibly held until that period ends — no early withdraw, no admin bailout of user deposits.\n\nChain: Ethereum mainnet (IMD existing token). Pair / deploy against the live IMD ERC-20; do not mint a new token.\n\nWhat to build\n\nSitOnHands.sol — a non-upgradeable lock vault for IMD only.\nUser calls lock(uint256 amount, uint256 durationSeconds) (or lockUntil(uint256 amount, uint256 unlockTimestamp) — pick one and stick to it). Transfer IMD in via transferFrom; create a position per deposit (support multiple concurrent locks per wallet).\nWhile locked: no withdraw, no transfer of the position, no emergency withdraw for users, no owner sweep of user balances. Only withdraw(uint256 positionId) (or equivalent) after block.timestamp >= unlockTime, returning the exact locked IMD to the depositor.\nOptional: emit Locked / Withdrawn with depositor, amount, unlockTime, positionId.\nOptional read helpers: positionOf, unlockTime, lockedBalance, canWithdraw.\nRules / constraints\n\nIMD address must be constructor-immutables (or a single immutable token).\nEnforce min/max duration (e.g. min 1 day, max 4 years) — document constants in the contract.\nReject zero amount and zero/invalid duration.\nReentrancy-safe withdraw (checks-effects-interactions or OZ ReentrancyGuard).\nNo fee on lock or unlock unless explicitly specified (default: 0 fee).\nNo reward / staking yield in v1 — pure sit-on-hands.\nOwner (if any) may only pause new locks, never seize or shorten existing locks. Prefer no owner if the factory allows a fully immutable vault.\nDo not add “sit on hands” marketing site unless asked; contract + tests + brief README is enough.\nTests (Foundry)\n\nLock then withdraw after warp succeeds for full amount.\nWithdraw before unlock reverts.\nTwo positions for same user unlock independently.\nCannot drain others’ positions.\nMin/max duration bounds enforced.\nMax duration = 365 days\nOut of scope\n\nGovernance, voting escrow (ve), boosts, LP locks, cross-token locks, forced lock of third parties (only the caller’s own IMD).\nAny sell/transfer restriction on the IMD token itself (locking is deposit-based only).\nAcceptance\n\nCompiles; Foundry tests pass; verified deploy script ready for mainnet IMD address; public lock + time-gated withdraw only.","parentJobId":null,"planHash":"15b6293b74abda9aff30af4dcad9df2e9784ea567d47081ba47ba389c2896279","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"2f49cf2e-f481-4aec-b90e-d01c17c90bae","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-849-title-sitonhands-voluntary"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51557","feedbackHash":"7607408483891801ff1b527a0378ae5dea96339d1c777ffe242e956dd1f40e2c","nodeKey":"audit_economics","submissionHash":"d2ad8cb98cddea8c69763f043bb92ab6da62fb5fbb190de0b470d6dbde98a0d2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51474","feedbackHash":"e2f0b5e26b9e6f4221abcd27b18df336829744dc75b954870568f838c56e541d","nodeKey":"audit_flow","submissionHash":"900327dee14b787aea2624fcc971d2fecb0ee8eef846d180e1840ceec305b332","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50969","feedbackHash":"1d8ea6f72eee6e145ebbd45a569e1f2c2e17c1bf8b872d9b7aad865e6c79ddeb","nodeKey":"audit_judge","submissionHash":"f8f153aaba5fe8d7e07bd93bddb44eb15bc4b25e72ee45fe6d379cccb7a3118b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51352","feedbackHash":"306f7c07b6daa53f3ace614772587a9d49ad37f6ef80b26a8d1b0ce7c7259b01","nodeKey":"audit_math","submissionHash":"160c48cc07282a59327823dd3d1b518b82a788f66d7099012a1a130605308531","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50989","feedbackHash":"c50d2ff8b3b89dea1494307325926049b1805c8d03678cb8992fbcbf693b91c4","nodeKey":"audit_permissions","submissionHash":"c0d8ca87ae68a0e18c3de401d9c01297051a3b4a337d044a027cd7ff59f4bfb2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52073","feedbackHash":"76103f07fef11a7931fc98fabe4c1967be1c4dafd791344278a119440f1b2c65","nodeKey":"build_contract_project","submissionHash":"5a3fd23f7b7aad750719b74546bf5c493ed8a8177a8d7735335b310006004c93","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52089","feedbackHash":"7dfbfcf7393d0f4aa09e21b7a3c840d6da95a5f13609cd2008d1185c7f1cdaf6","nodeKey":"manifest","submissionHash":"2403308e28ad7d7869918a9db1d9bccb5daa5daef917aef29aa1e93ec281e0e3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51539","feedbackHash":"f71de72c112cb67ef11449f8870dd68107470083affc8a3f44489df5bbcca3e3","nodeKey":"write_foundry_tests","submissionHash":"ee3aaab3d873471a95144b4062ecfe7ede318118abd0317c5d8be3907ac04c43","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"ac1908461bf4e60730cbcc44416b0edbc886fdace7f3d19e887803cfe73a9bd2","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8f08088e7a7f557f","findings":[{"citation":"resolved","description":"Not a vault defect and no fix is requested; recorded as the one material external-dependency risk found in the math/boundary review. withdraw() requires the vault's balance to drop by exactly `amount` AND the depositor's balance to rise by exactly `amount`, otherwise it reverts. lock() applies the mirror-image checks (lines 64-67). This is the correct choice for the brief's 'returning the exact locked IMD' requirement and it is what keeps donations, fee tokens and no-op transfers from corrupting accounting. The consequence is that the vault's liveness is conditional on the live IMD implementation (which this repository has not identified or verified: README states the address and behaviour are unverified) keeping exact-transfer semantics for the whole lock period. If IMD is upgradeable, has a switchable transfer tax, a reflection mechanism, a recipient blacklist, or a pause, then every matured position reverts with UnexpectedTokenBalance (or the token's own revert) and, because the vault deliberately has no owner, rescue, or sweep, the principal is unreachable until the token behaviour is restored. The actor is the IMD token's administrator (or its upgrade authority), not any vault role; no unprivileged amplifier exists, so under the Pashov validation gates this is documented rather than reported as a defect. Boundary exercised: the outbound safeTransfer call in withdraw and the inbound safeTransferFrom in lock. Assumption: IMD transfers move exactly `amount` and nothing else on both accounts. Actual under a fee-switch: vault balance drops by amount, depositor receives amount - fee, withdraw reverts permanently. The README already documents this (Custody assumptions section); the judge should confirm the IMD implementation's admin powers before release, as the README itself asks.","line":93,"path":"src/SitOnHands.sol","reproduction":"Mock reproduction (same mechanism as the existing test testFeeChargingWithdrawCanBeRetried, but without the token being switched back): 1) ALICE approves and calls lock(100, 1 days) with the token in Normal mode; totalLocked == 100, vault balance == 100. 2) warp to unlockTime. 3) token administrator enables a 10% transfer fee (MockIMD.setModes(Normal, Fee)). 4) ALICE calls withdraw(id): safeTransfer moves 100 out of the vault but credits ALICE 90, so `imd.balanceOf(msg.sender) != depositorBefore + amount` is true and the call reverts UnexpectedTokenBalance; state rolls back, vault still holds 100, totalLocked == 100. Expected by the brief: exact principal returned after maturity. Actual: no path returns it while the fee is active; there is no admin function to release it. Verified with test/scratch/Probe.t.sol::testTokenFeeEnabledAfterLockBricksWithdraw (passes, i.e. the revert is reproduced). Precondition is a privileged action on the external token, so severity is informational.","severity":"info","snippet":"        if (\n            vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n                || imd.balanceOf(msg.sender) != depositorBefore + amount\n        ) revert UnexpectedTokenBalance();","title":"Trust assumption: strict both-side balance equality makes principal unrecoverable if the live IMD token ever stops transferring exact amounts"}],"hash":"160c48cc07282a59327823dd3d1b518b82a788f66d7099012a1a130605308531","nodeId":"261ad84f-d4a7-4a95-8e0f-efbd1a813ef6","outcome":"completed","summary":"Done. The findings file is valid JSON, the quoted snippet matches lines 93–96 of the source, and the working tree shows no tracked changes (only the required `.imd-findings.json` and the gitignored scratch test were added).\n\n**Result: no math, boundary, or numerical-gap defects found in SitOnHands.** The contract has no division, scaling, or rounding at all, so the precision and seam failure modes in the assigned guides have nothing to attach to. Every edge I traced holds:\n\n- **Duration bounds** are inclusive at 86,400 and 31,536,000 seconds. The off-by-one values on both sides revert, matching the brief's 1-day minimum and 365-day maximum.\n- **Time gate** flips exactly at `unlockTime`, so a position is held for precisely the requested number of seconds.\n- **Arithmetic** cannot overflow or underflow. The unlock time sum is bounded by the 365-day cap, and both subtractions on balance deltas are short-circuited by an explicit less-than guard. An amount one below the uint256 maximum locks and withdraws exactly.\n- **Accounting** is only exact additions and subtractions of the transferred amount, and the strict both-side balance checks reject fee, no-op, false-return, and self-credit transfers. Reentrancy from token hooks into either entry point is blocked by the guard, and the mid-hook view state looks like a donation, so nothing observes a harmful inconsistency.\n- **Static-analysis leads** did not reproduce: the reentrancy lines are covered by the guard, the ETH-send line has no ETH path, and the timestamp lines are the intended gate.\n\nI recorded one informational item rather than a defect. The strict equality check that protects exactness also means that if the live IMD token ever stops transferring exact amounts (a fee switch, upgrade, blacklist, or pause by the token's own administrator), every matured withdrawal reverts and the ownerless vault has no recovery path. That actor is external and privileged, the README already documents it, and the judge should confirm the real IMD implementation's admin powers before release.\n\nCoverage has six rows: both listed entry points, the constructor, the views, and two invariants, all marked holds except withdraw, which carries the informational reference.","treeHash":null,"usage":{"cachedInputTokens":759457,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":16511,"runtime":"claude","turns":26,"wallClockMs":275720}},{"artifacts":[],"attempt":1,"bundleHash":"e1ca3c3acd0ccf07fe03911faa1fa733ae85466459d5fe22af68c63136b49f2c","device":"74db3e88f3c65478","findings":[],"hash":"2403308e28ad7d7869918a9db1d9bccb5daa5daef917aef29aa1e93ec281e0e3","nodeId":"0e492c48-d24f-47cd-8082-9cceced57c7b","outcome":"completed","summary":"Created [launch.json](/home/seat21/.identitymd/work/2f49cf2e-f481-4aec-b90e-d01c17c90bae/0e492c48-d24f-47cd-8082-9cceced57c7b/launch.json) using the existing mainnet token address from [IMD’s official page](https://imd.fun/token/).\n\nPassed schema and constructor ABI checks, offline build, all 45 tests, a second fuzz seed with 1,024 runs, protected deployment rehearsal, and formatting checks.\n\nOnly `launch.json` changed. Live-token compatibility was not tested.","treeHash":"577f95845769cd818f79f31b44d6b95dfd4cfbda","usage":{"cachedInputTokens":567552,"inputTokens":56339,"model":"gpt-6-astra","outputTokens":4116,"runtime":"codex","turns":4,"wallClockMs":132342}},{"artifacts":[],"attempt":1,"bundleHash":"b026bd91a135060b594cd94df235dcb31391bc15fc013c86e2d1959c99b45d64","device":"381cad4126e6ec64","findings":[],"hash":"5a3fd23f7b7aad750719b74546bf5c493ed8a8177a8d7735335b310006004c93","nodeId":"05613805-79d0-4fc0-b622-99dac5a4619d","outcome":"completed","summary":"Implemented the ownerless vault with independent 1–365-day locks, exact repayments, reentrancy protection, and no early exit. Added tests, vendored dependencies, deployment script, and README.\n\nChecks passed: `forge build`, all 45 tests, and `forge fmt --check`.\n\nThe canonical mainnet IMD address was not supplied and remains an explicit, unverified deployment parameter. No transactions were broadcast.","treeHash":"0bf7cdbc8c9bc7f7e8d2f4938ba5bde06f507d9e","usage":{"cachedInputTokens":551808,"inputTokens":61499,"model":"gpt-6-astra","outputTokens":19286,"runtime":"codex","turns":6,"wallClockMs":506927}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0e3b71e2ffcd200b","findings":[{"citation":"resolved","description":"Execution trace x periphery x first principles. withdraw() marks the position paid, transfers `amount`, then requires that the depositor's balance rose by exactly `amount` and the vault's fell by exactly `amount`, otherwise it reverts with UnexpectedTokenBalance and rolls everything back. The implicit assumption is that IMD's transfer accounting at withdrawal time is identical to what it was at lock time. The vault deploys against an existing, unverified mainnet ERC-20 whose administrator (not the vault's; the vault has no owner) may later enable a transfer tax, an exempt-list change, a reflection mechanism, or an upgrade that moves 1 wei of rounding. In every such case lock() correctly refused deposits while the behaviour was active, but deposits made BEFORE the change can never be withdrawn: the only exit path reverts deterministically forever, there is no owner, no rescue, no partial-delivery branch, and the position stays `withdrawn = false` with lockedBalance/totalLocked intact. The protocol's stated guarantee is 'funds are forcibly held until that period ends' and then returned; the end state here is funds held forever. This is a design trade-off the author chose deliberately (tests testFeeChargingWithdrawCanBeRetried and _assertFailedWithdrawal treat the revert as desired and assume the token will later 'recover'), and the README lists rebasing/confiscation/blacklisting/pausing as external risks, but a later-enabled fee is not named and is the most common mutable behaviour in deployed meme/community ERC-20s. Severity is low because the trigger is a third-party token-admin action, not an unprivileged attacker. Proposed minimal fix, which changes the 'exact amount' guarantee and therefore needs a scope decision: keep the vault-side check (`balanceOf(this) == vaultBefore - amount`) which proves the vault released exactly the principal, and drop or relax the depositor-side equality so a taxed delivery still completes rather than trapping 100% of the principal. Alternatively, keep the strict check but state this failure mode explicitly in the README's custody section so the release decision is made with it in view.","line":95,"path":"src/SitOnHands.sol","reproduction":"State: a minimal ERC-20 with an admin-settable tax (test/scratch/Probe.t.sol TaxableToken; MockIMD.Mode.Fee produces the same result). 1) vm.warp(1_000_000); deploy vault = new SitOnHands(token); mint 1000 to ALICE; ALICE approves and calls vault.lock(1000, 30 days) -> position 0, vault balance 1000. 2) token admin calls setTaxBps(100) (1% tax). 3) vm.warp(+30 days); vault.canWithdraw(0) == true. 4) ALICE calls vault.withdraw(0). Expected (protocol intent): ALICE receives her principal, or at worst the taxed amount, once the lock period ends. Actual: revert UnexpectedTokenBalance at src/SitOnHands.sol:93-96 because ALICE's balance rose by 990 not 1000; state rolled back; repeating 10 years later reverts identically. token.balanceOf(vault) == 1000, token.balanceOf(ALICE) == 0 permanently. Run: forge test --match-path test/scratch/Probe.t.sol --match-test testTaxEnabledAfterLockBricksWithdrawForever (passes, demonstrating the brick).","severity":"low","snippet":"                || imd.balanceOf(msg.sender) != depositorBefore + amount","title":"Strict post-transfer balance equality in withdraw() turns any later change in IMD transfer accounting into permanent, unrecoverable loss of all locked principal"},{"citation":"resolved","description":"Periphery lens on the deploy script. The try/catch only catches a reverting external call. If the call succeeds but the return data cannot be ABI-decoded as `string` (MKR-style tokens return `bytes32` from symbol()), decoding happens in the caller after the try block and reverts with empty return data, bypassing both the `returns` branch and the `catch`. The operator sees a bare revert rather than WrongTokenSymbol. No funds are at risk and the vault itself is unaffected; the script still refuses to deploy. Reported so the operator does not misread a bare revert as an RPC or chain problem if IMD happens to use a bytes32 symbol. Fix: use a low-level staticcall to the symbol() selector and compare the raw return data, or document that a bytes32-symbol token produces an undecorated revert.","line":30,"path":"script/DeploySitOnHands.s.sol","reproduction":"vm.chainId(1); deploy `contract Bytes32SymbolToken { function symbol() external pure returns (bytes32) { return \"IMD\"; } }`; call DeploySitOnHands.validate(address(token)). Expected: revert WrongTokenSymbol(). Actual: revert with 0 bytes of return data (test/scratch/Probe.t.sol testDeployScriptBytes32Symbol logs `validate returndata: 0x`).","severity":"info","snippet":"        try ITokenSymbol(imd).symbol() returns (string memory symbol) {","title":"DeploySitOnHands.validate() reverts with empty data instead of WrongTokenSymbol when the token's symbol() returns bytes32"},{"citation":"resolved","description":"Periphery integrity check. I hashed every vendored OpenZeppelin file against raw.githubusercontent.com at tag v5.0.2. IERC20.sol, SafeERC20.sol and ReentrancyGuard.sol match byte-for-byte. Address.sol (sha256 2e8da894... vs upstream b3710b17...) and IERC20Permit.sol (c558d22a... vs upstream 912509e0...) differ. A full diff shows only forge-fmt whitespace/line-wrapping changes (the `(bool success, )` tuple spacing, and multi-line parameter lists collapsed or re-wrapped); no token, operator or logic differs, so SafeERC20's empty-returndata and no-code checks behave exactly as upstream. This is reported at info level because the DEPENDENCIES.md statement is the artefact a verifier or later reviewer would rely on when deciding whether to re-audit the library, and a hash comparison will fail. Fix: either restore the byte-identical upstream files (exclude lib/ from forge fmt) or amend the sentence to say the files were reformatted with forge fmt and are semantically identical.","line":12,"path":"lib/DEPENDENCIES.md","reproduction":"sha256sum lib/openzeppelin-contracts/contracts/utils/Address.sol -> 2e8da894236dc598818e898ba20b31e294bf8592d156e780b845d36b61a35bad; curl -sL https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.0.2/contracts/utils/Address.sol | sha256sum -> b3710b1712637eb8c0df81912da3450da6ff67b0b3ed18146b033ed15b1aa3b9. Expected per DEPENDENCIES.md: equal. Actual: differ (whitespace only; `diff` shows 2 hunks in Address.sol and 1 in IERC20Permit.sol).","severity":"info","snippet":"Upstream Solidity source files are unmodified.","title":"lib/DEPENDENCIES.md claims vendored OpenZeppelin sources are unmodified, but Address.sol and IERC20Permit.sol are reformatted relative to v5.0.2"}],"hash":"900327dee14b787aea2624fcc971d2fecb0ee8eef846d180e1840ceec305b332","nodeId":"d68328c6-dda9-4075-82d0-e5f5a67b4fd5","outcome":"completed","summary":"The review is complete and the findings file is written at `.imd-findings.json`. The working tree shows no modified tracked files; only the ignored `test/scratch/` directory and the findings file were created.\n\n**Outcome: no critical, high, or medium defects in the two entry points.** Both `lock` and `withdraw` hold under the Execution Trace, Periphery, and First Principles checks. The build compiles and all 45 existing tests pass, including the fuzz and stateful invariant suites.\n\nWhat I verified in depth:\n- **lock**: zero-amount and out-of-bounds duration rejection, inclusive 1-day to 365-day bounds, pull-only-from-caller semantics so residual allowances cannot be spent by others, both-sided balance delta checks that reject fee, no-op, and false-returning tokens with full rollback, and reentrancy blocking across both entry points via token hooks.\n- **withdraw**: depositor-only access, inclusive time gate checked at unlock minus one second and at unlock for both 1-day and 365-day locks, double-withdraw and unknown-id rejection, checks-effects-interactions ordering with the reentrancy guard, and full rollback on transfer failure so retry remains possible.\n- **Conservation invariant**: vault balance is at least `totalLocked`, which equals the sum of per-depositor balances and the sum of unwithdrawn position amounts. Only lock and withdraw touch all three, in lockstep.\n- **No escape paths**: the ABI exposes only the two mutators and views. There is no owner, pause, upgrade, sweep, receive, or fallback.\n- **Vendored library integrity**: hashed every OpenZeppelin file against upstream v5.0.2.\n\nThree findings, none blocking:\n\n1. **Low.** The strict depositor-side balance equality in `withdraw` means that if the live IMD token's transfer accounting ever changes after locks exist, such as a token admin enabling a transfer tax, every pre-existing position becomes permanently unwithdrawable. There is no fallback, partial-delivery, or owner path by design. I reproduced this with a scratch test. The trigger requires third-party token-admin action, so it is a trust assumption the author should decide on consciously, not an attacker path. The proposed fix relaxes the \"exact amount\" guarantee, so it needs a scope decision.\n2. **Info.** The deploy script's symbol check reverts with empty return data rather than its named error for a token whose `symbol()` returns `bytes32`, because ABI decoding failures escape try/catch. Script only, no fund impact.\n3. **Info.** DEPENDENCIES.md states the vendored OpenZeppelin files are unmodified, but `Address.sol` and `IERC20Permit.sol` differ from upstream. The diff is whitespace and line wrapping only, with identical semantics, but a hash comparison will fail.\n\nThe coverage record has seven rows: both listed entry points plus the conservation invariant, the no-escape-path invariant, library integrity, the deploy script, and the absent launch manifest. One open deployment item remains outside the code: the live IMD address is not in the tree or in any network file, so token identity must be confirmed at the manifest step.","treeHash":null,"usage":{"cachedInputTokens":784150,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":22116,"runtime":"claude","turns":23,"wallClockMs":345339}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e36579e0223ff908","findings":[{"citation":"resolved","description":"lock() and withdraw() are symmetric in requiring exact balance deltas on both sides of the transfer. On the deposit side this check protects the vault (it refuses to credit principal it did not receive). On the withdrawal side the accounting has already been settled (position.withdrawn = true, lockedBalance/totalLocked decremented at lines 86-88) and the transfer amount is already fixed, so the check protects nothing in the vault: its only effect is to convert any token behaviour that delivers less than `amount` (fee, partial transfer, recipient-side hook) into a revert that repeats on every retry. The depositor then receives 0 forever rather than the reduced amount. The README acknowledges token-side changes can block withdrawals, but the vault itself makes the outcome strictly worse than a plain SafeERC20 transfer would. Reachability against the live token: I read the mainnet IMD contract at 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 over RPC (symbol() == \"IMD\", EIP-1967 implementation slot empty, runtime is a LayerZero OFT with standard OpenZeppelin ERC20 _update, no fee or blacklist path, owner 0x047f606fd5b2baa5f5c6c4ab8958e45cb6b054b7 can only set peers/name/symbol/delegate). That token cannot develop a fee or hook, so this is NOT exploitable on the intended deployment and is reported as informational for the author to decide, not as a blocking defect. It would become real only if the vault were pointed at a different or wrapped IMD.","line":93,"path":"src/SitOnHands.sol","reproduction":"Scratch test test/scratch/StrictWithdrawCheck.t.sol (passes on current code, demonstrating the behaviour): (1) alice approves and calls lock(1000e18, 1 days) against a token with 0 fee -> position 0 created, vault balance 1000e18. (2) The token's admin sets a 1% outbound fee. (3) warp +1 day; alice calls withdraw(0). Expected under a tolerant design: alice receives 990e18 and the position closes. Actual: revert UnexpectedTokenBalance() because imd.balanceOf(vault) == vaultBefore - amount holds but imd.balanceOf(alice) == 990e18 != depositorBefore + 1000e18; the revert rolls back the effects, so every later withdraw(0) fails the same way and alice's 1000e18 stays in the vault indefinitely. Minimal change preserving the agreed design: drop the recipient-side equality in withdraw() (keep the vault-side check vaultBefore - amount if desired), or drop the whole post-check since effects precede the transfer and SafeERC20 already reverts on failure.","severity":"info","snippet":"        if (\n            vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n                || imd.balanceOf(msg.sender) != depositorBefore + amount\n        ) revert UnexpectedTokenBalance();","title":"withdraw() post-transfer exact-balance check turns any token-side deviation into a permanent 100% freeze instead of a partial payout (Asymmetry step 6: defensive check that can only hurt)"},{"citation":"resolved","description":"Acceptance asks for a deploy script 'ready for mainnet IMD address'. The script takes the address as a free argument and authenticates it only by symbol(), which any contract can return, and which on the live IMD OFT (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7) is owner-mutable via setSymbol(string). The README states the canonical address was not supplied. The vault constructor is correct (single immutable address, no msg.sender use, works through the factory), so this is a launch-input gap rather than a contract defect: the launch.json constructorArgs for SitOnHands must carry the authenticated static IMD address, and the manifest reviewer must confirm it. I verified on mainnet that 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 has code, returns symbol \"IMD\", is not an EIP-1967 proxy, and has standard ERC20 transfer semantics; its canonical status still needs confirmation from the project's own deployment record.","line":30,"path":"script/DeploySitOnHands.s.sol","reproduction":"Deploy any ERC-20 whose symbol() returns \"IMD\" (e.g. MockIMD from test/mocks) on mainnet and call DeploySitOnHands.run(thatAddress): validate() passes (chain 1, code present, symbol matches) and a vault bound to the wrong token is deployed. Expected: the script or manifest pins the authenticated canonical address. Actual: any same-symbol token passes. No funds are at risk in the vault itself; the risk is deploying against the wrong token.","severity":"info","snippet":"        try ITokenSymbol(imd).symbol() returns (string memory symbol) {\n            if (keccak256(bytes(symbol)) != keccak256(\"IMD\")) revert WrongTokenSymbol();","title":"Deploy script validates only an owner-settable symbol; canonical IMD address is not pinned anywhere in the repository"}],"hash":"c0d8ca87ae68a0e18c3de401d9c01297051a3b4a337d044a027cd7ff59f4bfb2","nodeId":"9ec97bb2-a670-459d-917d-6a0ff9ea4570","outcome":"completed","summary":"The review is complete. Findings and coverage are written to `.imd-findings.json`. No files in `src/`, `test/` (outside `test/scratch/`), or configuration were changed.\n\n**Outcome: no exploitable defect in the Access Control, Trust Gap or Asymmetry areas.** Both state-changing entry points hold.\n\n**What I checked**\n\n- **Access control.** Only two state-changing functions exist. `lock` pulls tokens solely from `msg.sender`, so nobody can force-lock a third party or use the vault as a confused deputy for approvals. `withdraw` is gated on the stored depositor, maturity, and a one-shot `withdrawn` flag. There is no owner, pause, initializer, proxy, `receive` or `fallback`, and the constructor ignores `msg.sender`, so factory deployment traps no control.\n- **Asymmetry.** The lock and withdraw writers mirror each other on all three counters (`positions`, `lockedBalance`, `totalLocked`). `canWithdraw` uses the same predicates as `withdraw`. The repo's invariant suite already enforces conservation.\n- **Trust gap.** The only external privileged actor is the IMD token owner. I read the live mainnet token over RPC. It is a non-proxy LayerZero OFT with standard OpenZeppelin transfers; its owner can only set peers, name, symbol and delegate. No path lets that owner create an asymmetry against depositors.\n- **Static-analysis leads.** All reentrancy lines are covered by `nonReentrant` plus checks-effects-interactions, and five existing tests assert re-entry is blocked. The aderyn ETH-send lead is a false positive. These are recorded as checked in the coverage rows.\n\n**Two informational notes** were recorded, neither blocking:\n\n1. The post-transfer exact-balance check in `withdraw` cannot protect the vault, since accounting is already settled, and would turn any token-side shortfall into a permanent zero payout. I reproduced this in `test/scratch/StrictWithdrawCheck.t.sol`, but it is not reachable against the verified live token, so it is reported as info.\n2. The deploy script authenticates the token only by `symbol()`, which is owner-mutable on the live IMD contract, and no canonical address is pinned in the repository. The launch manifest step must carry the authenticated static address.\n\nLive token facts I verified on mainnet: address `0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7`, symbol \"IMD\", empty EIP-1967 implementation slot, owner `0x047f606fd5b2baa5f5c6c4ab8958e45cb6b054b7`. Its canonical status still needs confirmation from the project's deployment record.\n\nSources: [DropsTab IMD listing](https://dropstab.com/coins/Identity-imd)","treeHash":null,"usage":{"cachedInputTokens":907180,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":17965,"runtime":"claude","turns":31,"wallClockMs":299407}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"559cfaaab2c0d013","findings":[{"citation":"resolved","description":"Economic Security / Flow Gap (seam: periphery x first principles). The vault's only external dependency is the live IMD token, whose implementation the README itself says has not been verified (lines 47-50, 94-97). withdraw() requires the vault's balance to drop by exactly `amount` AND the depositor's balance to rise by exactly `amount`, otherwise it reverts with UnexpectedTokenBalance and rolls back the position for retry. If IMD applies any deduction or redistribution on transfers (fee-on-transfer, reflection/holder redistribution, burn-on-transfer, or an upgrade/admin toggle introducing one), the equality can never hold again, so every position in the vault becomes permanently unwithdrawable. There is no owner, sweep, alternate recipient, or relaxed path, so the strictness changes the outcome from 'depositor receives amount minus the token's fee' to 'depositor receives nothing, forever'. The same strictness in lock() is harmless (the user keeps their tokens). This is a design trade-off against the brief's 'exact locked IMD' promise and is partially covered by the README's warning that a later change in the token's implementation can make withdrawals impossible, so it is reported as a trust assumption at low severity, not a logic bug. Mitigation options for the author to weigh: verify on-chain that the canonical IMD is a plain, non-upgradeable ERC-20 with exact transfers before deployment (the deploy script only checks chain id, code presence and symbol); or, if any doubt remains, relax the recipient-side check to `>=`/actual-received semantics so a token-level fee degrades to partial loss instead of total loss. Either choice is a scope decision for the requester.","line":93,"path":"src/SitOnHands.sol","reproduction":"State: FeeSwitchToken (plain ERC-20 with a settable outbound fee, fee=0 at start) deployed; vault = new SitOnHands(token). Alice and Bob each mint 1_000e18, approve the vault, and call lock(1_000e18, 30 days). totalLocked == 2_000e18. Then the token's fee is set to 1 bps (0.01%), simulating a fee enabled by the token after deposits exist. vm.warp(+30 days); canWithdraw(a) and canWithdraw(b) both return true. Expected (per brief): depositors recover their principal (or at worst principal minus the token's fee). Actual: alice.withdraw(a) reverts UnexpectedTokenBalance because imd.balanceOf(alice) == 1_000e18 - 1e14 != depositorBefore + amount; bob.withdraw(b) reverts identically; after warp(+10 years) it still reverts. token.balanceOf(vault) == 2_000e18, alice and bob hold 0, and no function on the vault can move the 2_000e18. Verified with a scratch Foundry test (3/3 passing assertions on this behaviour).","severity":"low","snippet":"        if (\n            vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n                || imd.balanceOf(msg.sender) != depositorBefore + amount\n        ) revert UnexpectedTokenBalance();","title":"Strict both-sides balance-delta check in withdraw converts any non-exact IMD transfer into permanent loss of 100% of all principal"}],"hash":"d2ad8cb98cddea8c69763f043bb92ab6da62fb5fbb190de0b470d6dbde98a0d2","nodeId":"24ee5ade-8374-4be7-b5df-06e2e04f02d9","outcome":"completed","summary":"Review complete. The findings file is valid JSON, the quoted snippet matches lines 93 to 96 of the contract exactly, the scratch test is removed, and the working tree has no tracked changes.\n\n**Outcome.** The vault is sound in my area. I found no defect at medium or above and recorded one low-severity trust assumption.\n\n**What I checked** against the Economic Security, Invariant and Flow Gap guides:\n\n- **Conservation.** Only lock and withdraw write the coupled terms, and each updates position, per-user balance and total together. The existing stateful invariant suite passes, including donations.\n- **Round trip.** A deposit of X returns exactly X with an exact-transfer token. There is no fee, division, loop, shared cap or queue, so no rounding farming, griefing or blocking of other depositors is possible.\n- **Access and privilege.** No owner, pause, sweep, fallback, delegatecall or selfdestruct exists. Positions are written once and never transferred or shortened.\n- **Time boundary.** Withdrawal succeeds at exactly the unlock timestamp and fails one second earlier, matching the brief.\n- **Static-analysis leads.** The Slither and Aderyn reentrancy and ETH-send lines are false positives. Both entry points are guarded and no ETH moves.\n- **Dependency integrity.** Vendored OpenZeppelin and forge-std files match their upstream tags byte for byte, or differ only by formatter whitespace. There are no submodules.\n\n**The one finding (low).** The withdraw function demands the vault balance fall by exactly the amount and the depositor balance rise by exactly the amount. If the live IMD token ever applies any deduction on transfers, whether a fee, reflection, or a later upgrade, every position becomes permanently unwithdrawable with no recovery path. A scratch test reproduced this: two depositors, a 0.01% fee switched on after locking, and both withdrawals revert forever while the vault keeps the full principal. This is a design trade-off against the brief's exact-principal promise rather than a logic bug, and the README partly warns about it. The author should either verify on-chain that canonical IMD is a plain non-upgradeable ERC-20, or accept a relaxed recipient-side check so a token fee degrades to partial loss instead of total loss.\n\n**Open item, not a defect.** The canonical mainnet IMD address is still unknown to the repository, so the constructor argument for the launch manifest remains an input the deployer must supply and verify.","treeHash":null,"usage":{"cachedInputTokens":895100,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":19927,"runtime":"claude","turns":29,"wallClockMs":366760}},{"artifacts":[],"attempt":1,"bundleHash":"61642e222fd1ff62117eeee857b8b05c8e8092acab93b312b6cea7beecbd4e4e","device":"699fb03b7f482edb","findings":[],"hash":"ee3aaab3d873471a95144b4062ecfe7ede318118abd0317c5d8be3907ac04c43","nodeId":"107e5cfa-bcee-45e3-bfaa-9843e370bc78","outcome":"completed","summary":"Added boundary, failure-path, and three-actor invariant tests exclusively under `test/`.\n\nOffline `forge build` and `forge test` pass: **59 tests**, including 4,000 new fuzz cases and 24,576 new invariant calls.\n\nNo defect reproduced. Live IMD compatibility remains unverified because no canonical token address was supplied.","treeHash":"c97c5fa4f5247bd4ef0990dd910e4e46fec6b8ef","usage":{"cachedInputTokens":709632,"inputTokens":79226,"model":"gpt-6-astra","outputTokens":13356,"runtime":"codex","turns":6,"wallClockMs":366152}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"93ca4a1020037bf1","findings":[],"hash":"f8f153aaba5fe8d7e07bd93bddb44eb15bc4b25e72ee45fe6d379cccb7a3118b","nodeId":"d3b82587-fbcb-4ad7-bcad-8117753854aa","outcome":"completed","summary":"I found no defects that need fixing, so `.imd-findings.json` has an empty findings list. Its coverage record marks both entry points as `holds`, plus two checks I added: no admin path to user funds, and the launch constructor input. The full suite passes (59/59). No repository files were changed; the only file written is `.imd-findings.json`.\n\n**Both entry points behave as the brief asks:**\n- **`lock`** has a reentrancy guard and rejects a zero amount or a duration outside 1–365 days (both ends allowed). It takes only the caller's own IMD, checks that both balances moved by exactly the amount, and opens a new position each time. Position IDs are never reused.\n- **`withdraw`** checks the position exists, that the caller deposited it, that it hasn't been paid out, and that `block.timestamp >= unlockTime`. It marks the position paid before sending back the exact amount, and a failed transfer rolls everything back so it can be retried.\n- The vault has no owner, fee, pause, sweep, upgrade or other way out.\n\n**Why I dropped every specialist finding:**\n- **Withdrawals stuck if IMD later adds a transfer fee** (math, permissions, economics and flow all reported this; I treated it as one issue). It doesn't apply to the real token. I checked the IMD address in `launch.json` (`0xD34a…63B7`) on mainnet: `symbol()` returns \"IMD\", it is not a proxy, and it has no fee, pause or blacklist functions. Its owner can only change peers, name, symbol and delegate. So an exact-amount transfer can't start failing after a deposit.\n- **Deploy script only checks the symbol:** `launch.json` already fixes the token address, so the symbol check doesn't decide which token the vault uses.\n- **Deploy script gives an unclear error for tokens whose `symbol()` returns `bytes32`:** the real IMD returns a normal string, so this never happens with it.\n- **`lib/DEPENDENCIES.md` says the vendored OpenZeppelin files are unmodified, but two are reformatted:** the specialist's diff shows only whitespace changes. That's a documentation note, not a contract defect.\n- **Static-analysis warnings:** the reentrancy warnings are covered by the reentrancy guards and the fixed, trusted token. The \"ETH sent without address check\" warning is wrong, because the contract never sends ETH. The timestamp warnings are just the intended unlock-time check.","treeHash":null,"usage":{"cachedInputTokens":293881,"inputTokens":12,"model":"claude-opus-5-5","outputTokens":3546,"runtime":"claude","turns":6,"wallClockMs":81024}}],"verification":[{"checks":[{"durationMs":1861,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.70s\nCompiler run successful!\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:65:41\n   │\n65 │             depositorBefore < amount || imd.balanceOf(msg.sender) != depositorBefore - amount\n   │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:66:20\n   │\n66 │                 || imd.balanceOf(address(this)) != beforeBalance + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SitOnHands.sol:83:13\n   │\n83 │         if (block.timestamp < position.unlockTime) revert StillLocked(position.unlockTime);\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:94:37\n   │\n94 │             vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n   │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:95:20\n   │\n95 │                 || imd.balanceOf(msg.sender) != depositorBefore + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SitOnHands.sol:104:75\n    │\n104 │         return position.depositor != address(0) && !position.withdrawn && block.timestamp >= position.unlockTime;\n    │                                                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":2393,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/DeploySitOnHands.t.sol:DeploySitOnHandsTest\n[PASS] testApplicationRuntimeMatchesProtectedOpcodeAndSizeRules() (gas: 1463713)\n[PASS] testConstructorIsIndependentOfExternalTokenState() (gas: 257512)\n[PASS] testFactoryDeploymentAndFullUserLifecycle() (gas: 645022)\n[PASS] testRunRejectsMissingSymbol() (gas: 221906)\n[PASS] testRunRejectsWrongChain() (gas: 32495)\n[PASS] testRunRejectsWrongSymbol() (gas: 69082)\n[PASS] testRunRejectsZeroTokenAndAddressWithoutCode() (gas: 56065)\n[PASS] testRunUsesExplicitTokenOnMainnet() (gas: 807896)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.99ms (11.72ms CPU time)\n\nRan 36 tests for test/SitOnHands.t.sol:SitOnHandsTest\n[PASS] testCannotReenterLockDuringWithdrawal() (gas: 603141)\n[PASS] testCannotReenterWithdrawalDuringDeposit() (gas: 618907)\n[PASS] testCannotWithdrawOthersPositionEvenAfterMaturity() (gas: 297454)\n[PASS] testCannotWithdrawTwice() (gas: 336802)\n[PASS] testConstructorAndBounds() (gas: 27059)\n[PASS] testDonationDoesNotChangePrincipalOrMaturity() (gas: 406327)\n[PASS] testFalseReturningWithdrawCanBeRetried() (gas: 553288)\n[PASS] testFeeChargingWithdrawCanBeRetried() (gas: 567365)\n[PASS] testFuzzFullPrincipalAndTimeGate(uint256,uint256,uint256) (runs: 256, μ: 371411, ~: 371223)\nLogs:\n  Bound result 483500\n  Bound result 2250462\n  Bound result 107895\n\n[PASS] testFuzzInvalidDuration(uint256) (runs: 256, μ: 42683, ~: 42623)\n[PASS] testInsufficientApprovalRollsBack() (gas: 99087)\n[PASS] testInsufficientBalanceRollsBack() (gas: 105884)\n[PASS] testLockAndWithdrawFullAmountExactlyAtUnlock() (gas: 475050)\n[PASS] testLockOnlySpendsCallersApprovedTokens() (gas: 297359)\n[PASS] testMaximumDurationIsAcceptedAndEnforced() (gas: 339760)\n[PASS] testNoPositionTransferOrAdminEscapeSelectors() (gas: 352338)\n[PASS] testOtherTokensCannotFundAPosition() (gas: 1014687)\n[PASS] testReentrantDepositIsBlocked() (gas: 527321)\n[PASS] testReentrantWithdrawalOfOtherMaturedPositionIsBlocked() (gas: 745286)\n[PASS] testReentrantWithdrawalOfSamePositionIsBlocked() (gas: 566272)\n[PASS] testRejectsEther() (gas: 36152)\n[PASS] testRejectsExtraSenderFeeOnDepositAndRollsBack() (gas: 198539)\n[PASS] testRejectsFalseReturningDeposit() (gas: 166398)\n[PASS] testRejectsFeeOnDepositAndRollsBack() (gas: 199573)\n[PASS] testRejectsInvalidDurations() (gas: 172038)\n[PASS] testRejectsRevertingDeposit() (gas: 165749)\n[PASS] testRejectsSuccessfulNoOpDeposit() (gas: 167335)\n[PASS] testRejectsZeroAmount() (gas: 35306)\n[PASS] testRejectsZeroToken() (gas: 3865)\n[PASS] testRevertingWithdrawCanBeRetried() (gas: 550580)\n[PASS] testSenderFeeCannotConsumeAnotherUsersPrincipal() (gas: 758804)\n[PASS] testSuccessfulNoOpWithdrawCanBeRetried() (gas: 552272)\n[PASS] testSupportsNoReturnTokenTransfers() (gas: 361756)\n[PASS] testTwoPositionsUnlockIndependently() (gas: 613370)\n[PASS] testUnknownPositionRevertsAndIsNotWithdrawable() (gas: 55863)\n[PASS] testWithdrawOneSecondEarlyReverts() (gas: 280778)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 19.02ms (40.37ms CPU time)\n\nRan 1 test for test/SitOnHands.invariant.t.sol:SitOnHandsInvariantTest\n[PASS] invariantPrincipalIsConservedAndPositionsNeverChangeOwnerOrDeadline() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-----------------------------------+-------+---------+----------╮\n| Contract     | Selector                          | Calls | Reverts | Discards |\n+===============================================================================+\n| VaultHandler | advanceTime                       | 1375  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptEarlyOrDuplicateWithdrawal | 1400  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptForeignWithdrawal          | 1320  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | deposit                           | 1354  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | donate                            | 1297  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | withdraw                          | 1446  | 0       | 0        |\n╰--------------+-----------------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3\n  Bound result 7264\n  Bound result 1000000000000000000000000\n  Bound result 8193\n  Bound result 95\n  Bound result 31457848\n  Bound result 1835\n  Bound result 500\n  Bound result 31454261\n  Bound result 4660\n  Bound result 4660\n  Bound result 659918\n  Bound result 7124\n  Bound result 95\n  Bound result 1\n  Bound result 259200\n  Bound result 1000000\n  Bound result 2592000\n  Bound result 1430\n  Bound result 8306\n  Bound result 815271112212909596026167\n  Bound result 18652150\n  Bound result 910\n  Bound result 3195080\n  Bound result 3388981087734951\n  Bound result 2006622\n  Bound result 2576790293\n  Bound result 31536000\n  Bound result 95\n  Bound result 4660\n  Bound result 7565\n  Bound result 57\n  Bound result 500\n  Bound result 86403\n  Bound result 244\n  Bound result 31457550\n  Bound result 6\n  Bound result 31449606\n  Bound result 2718\n  Bound result 2951\n  Bound result 31536000\n  Bound result 3858\n  Bound result 8230\n  Bound result 2667\n  Bound result 5493\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.27s (2.27s CPU time)\n\nRan 3 test suites in 2.28s (2.30s CPU time): 45 tests passed, 0 failed, 0 skipped (45 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SitOnHands.lock(uint256,uint256)\",\"SitOnHands.withdraw(uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":106,\"foundry.toml\":25,\"launch.json\":12,\"remappings.txt\":2,\"script/DeploySitOnHands.s.sol\":36,\"src/SitOnHands.sol\":106,\"test/DeploySitOnHands.t.sol\":97,\"test/SitOnHands.invariant.t.sol\":160,\"test/SitOnHands.t.sol\":447,\"test/mocks/MockIMD.sol\":92},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2403308e28ad7d7869918a9db1d9bccb5daa5daef917aef29aa1e93ec281e0e3","verifiedTreeHash":"577f95845769cd818f79f31b44d6b95dfd4cfbda","verifierVersion":"0.1.0+94826a22"},{"checks":[{"durationMs":1878,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.72s\nCompiler run successful!\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:65:41\n   │\n65 │             depositorBefore < amount || imd.balanceOf(msg.sender) != depositorBefore - amount\n   │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:66:20\n   │\n66 │                 || imd.balanceOf(address(this)) != beforeBalance + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SitOnHands.sol:83:13\n   │\n83 │         if (block.timestamp < position.unlockTime) revert StillLocked(position.unlockTime);\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:94:37\n   │\n94 │             vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n   │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:95:20\n   │\n95 │                 || imd.balanceOf(msg.sender) != depositorBefore + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SitOnHands.sol:104:75\n    │\n104 │         return position.depositor != address(0) && !position.withdrawn && block.timestamp >= position.unlockTime;\n    │                                                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":2414,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/DeploySitOnHands.t.sol:DeploySitOnHandsTest\n[PASS] testApplicationRuntimeMatchesProtectedOpcodeAndSizeRules() (gas: 1463713)\n[PASS] testConstructorIsIndependentOfExternalTokenState() (gas: 257512)\n[PASS] testFactoryDeploymentAndFullUserLifecycle() (gas: 645022)\n[PASS] testRunRejectsMissingSymbol() (gas: 221906)\n[PASS] testRunRejectsWrongChain() (gas: 32495)\n[PASS] testRunRejectsWrongSymbol() (gas: 69082)\n[PASS] testRunRejectsZeroTokenAndAddressWithoutCode() (gas: 56065)\n[PASS] testRunUsesExplicitTokenOnMainnet() (gas: 807896)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.67ms (5.73ms CPU time)\n\nRan 36 tests for test/SitOnHands.t.sol:SitOnHandsTest\n[PASS] testCannotReenterLockDuringWithdrawal() (gas: 603141)\n[PASS] testCannotReenterWithdrawalDuringDeposit() (gas: 618907)\n[PASS] testCannotWithdrawOthersPositionEvenAfterMaturity() (gas: 297454)\n[PASS] testCannotWithdrawTwice() (gas: 336802)\n[PASS] testConstructorAndBounds() (gas: 27059)\n[PASS] testDonationDoesNotChangePrincipalOrMaturity() (gas: 406327)\n[PASS] testFalseReturningWithdrawCanBeRetried() (gas: 553288)\n[PASS] testFeeChargingWithdrawCanBeRetried() (gas: 567365)\n[PASS] testFuzzFullPrincipalAndTimeGate(uint256,uint256,uint256) (runs: 256, μ: 371533, ~: 371235)\nLogs:\n  Bound result 1000000\n  Bound result 31449697\n  Bound result 659918\n\n[PASS] testFuzzInvalidDuration(uint256) (runs: 256, μ: 42696, ~: 42659)\n[PASS] testInsufficientApprovalRollsBack() (gas: 99087)\n[PASS] testInsufficientBalanceRollsBack() (gas: 105884)\n[PASS] testLockAndWithdrawFullAmountExactlyAtUnlock() (gas: 475050)\n[PASS] testLockOnlySpendsCallersApprovedTokens() (gas: 297359)\n[PASS] testMaximumDurationIsAcceptedAndEnforced() (gas: 339760)\n[PASS] testNoPositionTransferOrAdminEscapeSelectors() (gas: 352338)\n[PASS] testOtherTokensCannotFundAPosition() (gas: 1014687)\n[PASS] testReentrantDepositIsBlocked() (gas: 527321)\n[PASS] testReentrantWithdrawalOfOtherMaturedPositionIsBlocked() (gas: 745286)\n[PASS] testReentrantWithdrawalOfSamePositionIsBlocked() (gas: 566272)\n[PASS] testRejectsEther() (gas: 36152)\n[PASS] testRejectsExtraSenderFeeOnDepositAndRollsBack() (gas: 198539)\n[PASS] testRejectsFalseReturningDeposit() (gas: 166398)\n[PASS] testRejectsFeeOnDepositAndRollsBack() (gas: 199573)\n[PASS] testRejectsInvalidDurations() (gas: 172038)\n[PASS] testRejectsRevertingDeposit() (gas: 165749)\n[PASS] testRejectsSuccessfulNoOpDeposit() (gas: 167335)\n[PASS] testRejectsZeroAmount() (gas: 35306)\n[PASS] testRejectsZeroToken() (gas: 3865)\n[PASS] testRevertingWithdrawCanBeRetried() (gas: 550580)\n[PASS] testSenderFeeCannotConsumeAnotherUsersPrincipal() (gas: 758804)\n[PASS] testSuccessfulNoOpWithdrawCanBeRetried() (gas: 552272)\n[PASS] testSupportsNoReturnTokenTransfers() (gas: 361756)\n[PASS] testTwoPositionsUnlockIndependently() (gas: 613370)\n[PASS] testUnknownPositionRevertsAndIsNotWithdrawable() (gas: 55863)\n[PASS] testWithdrawOneSecondEarlyReverts() (gas: 280778)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 25.14ms (39.61ms CPU time)\n\nRan 1 test for test/SitOnHands.invariant.t.sol:SitOnHandsInvariantTest\n[PASS] invariantPrincipalIsConservedAndPositionsNeverChangeOwnerOrDeadline() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-----------------------------------+-------+---------+----------╮\n| Contract     | Selector                          | Calls | Reverts | Discards |\n+===============================================================================+\n| VaultHandler | advanceTime                       | 1425  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptEarlyOrDuplicateWithdrawal | 1393  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptForeignWithdrawal          | 1310  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | deposit                           | 1374  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | donate                            | 1345  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | withdraw                          | 1345  | 0       | 0        |\n╰--------------+-----------------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 27418137743940\n  Bound result 300\n  Bound result 249\n  Bound result 7161\n  Bound result 787107\n  Bound result 799\n  Bound result 31450158\n  Bound result 11155111\n  Bound result 2483612\n  Bound result 143\n  Bound result 31535997\n  Bound result 51966\n  Bound result 7792\n  Bound result 31452421\n  Bound result 2036777\n  Bound result 96\n  Bound result 86400\n  Bound result 1\n  Bound result 86400\n  Bound result 5\n  Bound result 2178\n  Bound result 380603\n  Bound result 35814125\n  Bound result 31456509\n  Bound result 1666735\n  Bound result 2530193\n  Bound result 5575\n  Bound result 6384\n  Bound result 31449801\n  Bound result 659918\n  Bound result 31474177\n  Bound result 1481201\n  Bound result 830152672418353411861820\n  Bound result 259200\n  Bound result 255\n  Bound result 25194436\n  Bound result 99\n  Bound result 999999999999999999999997\n  Bound result 1\n  Bound result 31453511\n  Bound result 31536000\n  Bound result 259200\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.28s (2.28s CPU time)\n\nRan 3 test suites in 2.28s (2.31s CPU time): 45 tests passed, 0 failed, 0 skipped (45 total tests)\n","passed":true},{"durationMs":51,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SitOnHands.lock(uint256,uint256)\",\"SitOnHands.withdraw(uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":106,\"foundry.toml\":25,\"remappings.txt\":2,\"script/DeploySitOnHands.s.sol\":36,\"src/SitOnHands.sol\":106,\"test/DeploySitOnHands.t.sol\":97,\"test/SitOnHands.invariant.t.sol\":160,\"test/SitOnHands.t.sol\":447,\"test/mocks/MockIMD.sol\":92},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":538,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/SitOnHands.sol:56: Reentrancy in SitOnHands.lock(uint256,uint256) (src/SitOnHands.sol#56-74):\n[high/medium] reentrancy-balance at src/SitOnHands.sol:78: Reentrancy in SitOnHands.withdraw(uint256) (src/SitOnHands.sol#78-99):\n[high/medium] reentrancy-balance at src/SitOnHands.sol:56: Reentrancy in SitOnHands.lock(uint256,uint256) (src/SitOnHands.sol#56-74):\n[high/medium] reentrancy-balance at src/SitOnHands.sol:78: Reentrancy in SitOnHands.withdraw(uint256) (src/SitOnHands.sol#78-99):\n[low/medium] reentrancy-benign at src/SitOnHands.sol:56: Reentrancy in SitOnHands.lock(uint256,uint256) (src/SitOnHands.sol#56-74):\n[low/medium] timestamp at src/SitOnHands.sol:102: SitOnHands.canWithdraw(uint256) (src/SitOnHands.sol#102-105) uses timestamp for comparisons\n[low/medium] timestamp at src/SitOnHands.sol:78: SitOnHands.withdraw(uint256) (src/SitOnHands.sol#78-99) uses timestamp for comparisons","passed":true},{"durationMs":233,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/SitOnHands.sol:56: ETH transferred without address checks\n[high] reentrancy-state-change at src/SitOnHands.sol:61: Reentrancy: State change after external call (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5a3fd23f7b7aad750719b74546bf5c493ed8a8177a8d7735335b310006004c93","verifiedTreeHash":"0bf7cdbc8c9bc7f7e8d2f4938ba5bde06f507d9e","verifierVersion":"0.1.0+94826a22"},{"checks":[{"durationMs":2168,"exitCode":0,"name":"build","output":"Compiling 33 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.04s\nCompiler run successful!\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:65:41\n   │\n65 │             depositorBefore < amount || imd.balanceOf(msg.sender) != depositorBefore - amount\n   │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:66:20\n   │\n66 │                 || imd.balanceOf(address(this)) != beforeBalance + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SitOnHands.sol:83:13\n   │\n83 │         if (block.timestamp < position.unlockTime) revert StillLocked(position.unlockTime);\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:94:37\n   │\n94 │             vaultBefore < amount || imd.balanceOf(address(this)) != vaultBefore - amount\n   │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SitOnHands.sol:95:20\n   │\n95 │                 || imd.balanceOf(msg.sender) != depositorBefore + amount\n   │                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SitOnHands.sol:104:75\n    │\n104 │         return position.depositor != address(0) && !position.withdrawn && block.timestamp >= position.unlockTime;\n    │                                                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":7836,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/DeploySitOnHands.t.sol:DeploySitOnHandsTest\n[PASS] testApplicationRuntimeMatchesProtectedOpcodeAndSizeRules() (gas: 1463713)\n[PASS] testConstructorIsIndependentOfExternalTokenState() (gas: 257512)\n[PASS] testFactoryDeploymentAndFullUserLifecycle() (gas: 645022)\n[PASS] testRunRejectsMissingSymbol() (gas: 221906)\n[PASS] testRunRejectsWrongChain() (gas: 32495)\n[PASS] testRunRejectsWrongSymbol() (gas: 69082)\n[PASS] testRunRejectsZeroTokenAndAddressWithoutCode() (gas: 56065)\n[PASS] testRunUsesExplicitTokenOnMainnet() (gas: 807896)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.80ms (8.30ms CPU time)\n\nRan 36 tests for test/SitOnHands.t.sol:SitOnHandsTest\n[PASS] testCannotReenterLockDuringWithdrawal() (gas: 603141)\n[PASS] testCannotReenterWithdrawalDuringDeposit() (gas: 618907)\n[PASS] testCannotWithdrawOthersPositionEvenAfterMaturity() (gas: 297454)\n[PASS] testCannotWithdrawTwice() (gas: 336802)\n[PASS] testConstructorAndBounds() (gas: 27059)\n[PASS] testDonationDoesNotChangePrincipalOrMaturity() (gas: 406327)\n[PASS] testFalseReturningWithdrawCanBeRetried() (gas: 553288)\n[PASS] testFeeChargingWithdrawCanBeRetried() (gas: 567365)\n[PASS] testFuzzFullPrincipalAndTimeGate(uint256,uint256,uint256) (runs: 256, μ: 371436, ~: 371235)\nLogs:\n  Bound result 407050\n  Bound result 16885619\n  Bound result 5856888\n\n[PASS] testFuzzInvalidDuration(uint256) (runs: 256, μ: 42711, ~: 42689)\n[PASS] testInsufficientApprovalRollsBack() (gas: 99087)\n[PASS] testInsufficientBalanceRollsBack() (gas: 105884)\n[PASS] testLockAndWithdrawFullAmountExactlyAtUnlock() (gas: 475050)\n[PASS] testLockOnlySpendsCallersApprovedTokens() (gas: 297359)\n[PASS] testMaximumDurationIsAcceptedAndEnforced() (gas: 339760)\n[PASS] testNoPositionTransferOrAdminEscapeSelectors() (gas: 352338)\n[PASS] testOtherTokensCannotFundAPosition() (gas: 1014687)\n[PASS] testReentrantDepositIsBlocked() (gas: 527321)\n[PASS] testReentrantWithdrawalOfOtherMaturedPositionIsBlocked() (gas: 745286)\n[PASS] testReentrantWithdrawalOfSamePositionIsBlocked() (gas: 566272)\n[PASS] testRejectsEther() (gas: 36152)\n[PASS] testRejectsExtraSenderFeeOnDepositAndRollsBack() (gas: 198539)\n[PASS] testRejectsFalseReturningDeposit() (gas: 166398)\n[PASS] testRejectsFeeOnDepositAndRollsBack() (gas: 199573)\n[PASS] testRejectsInvalidDurations() (gas: 172038)\n[PASS] testRejectsRevertingDeposit() (gas: 165749)\n[PASS] testRejectsSuccessfulNoOpDeposit() (gas: 167335)\n[PASS] testRejectsZeroAmount() (gas: 35306)\n[PASS] testRejectsZeroToken() (gas: 3865)\n[PASS] testRevertingWithdrawCanBeRetried() (gas: 550580)\n[PASS] testSenderFeeCannotConsumeAnotherUsersPrincipal() (gas: 758804)\n[PASS] testSuccessfulNoOpWithdrawCanBeRetried() (gas: 552272)\n[PASS] testSupportsNoReturnTokenTransfers() (gas: 361756)\n[PASS] testTwoPositionsUnlockIndependently() (gas: 613370)\n[PASS] testUnknownPositionRevertsAndIsNotWithdrawable() (gas: 55863)\n[PASS] testWithdrawOneSecondEarlyReverts() (gas: 280778)\nSuite result: ok. 36 passed; 0 failed; 0 skipped; finished in 97.10ms (124.08ms CPU time)\n\nRan 12 tests for test/SitOnHands.edges.t.sol:SitOnHandsEdgesTest\n[PASS] testApprovedTokenSpenderCannotTakePosition() (gas: 518148)\n[PASS] testCallerOwnsPositionEvenWhenOriginDiffers() (gas: 492529)\n[PASS] testEntireUint256SupplyRoundTrip() (gas: 576968)\n[PASS] testFuzzAggregateAtUint256LimitDoesNotTruncate(uint256,bool) (runs: 1000, μ: 859223, ~: 859040)\nLogs:\n  Bound result 41810930449952740276706496604733939711238822635807696625393972315\n\n[PASS] testFuzzBothInvalidDurationRangesPreserveExistingLock(uint256) (runs: 1000, μ: 570654, ~: 570698)\nLogs:\n  Bound result 20\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913098103955\n\n[PASS] testFuzzFullWidthAmountsReturnExactly(uint256,uint256) (runs: 1000, μ: 583523, ~: 583362)\nLogs:\n  Bound result 245\n  Bound result 6405253\n\n[PASS] testFuzzUnknownIdsCannotSpendExistingPrincipal(uint256) (runs: 1000, μ: 508770, ~: 508708)\nLogs:\n  Bound result 3\n\n[PASS] testMaturedPrincipalRemainsOwedWithoutExpiryOrYield() (gas: 480672)\n[PASS] testOneWeiRoundTrip() (gas: 575795)\n[PASS] testRepeatedFullBalanceRelocksNeverReuseIdsOrPayOldClaims() (gas: 5003002)\n[PASS] testRevokedAllowanceAndEmptyWalletDoNotBlockMatureWithdrawal() (gas: 566290)\n[PASS] testWithdrawEffectsAreVisibleBeforeTokenCallback() (gas: 1352363)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 97.22ms (378.41ms CPU time)\n\nRan 1 test for test/SitOnHands.invariant.t.sol:SitOnHandsInvariantTest\n[PASS] invariantPrincipalIsConservedAndPositionsNeverChangeOwnerOrDeadline() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-----------------------------------+-------+---------+----------╮\n| Contract     | Selector                          | Calls | Reverts | Discards |\n+===============================================================================+\n| VaultHandler | advanceTime                       | 1352  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptEarlyOrDuplicateWithdrawal | 1412  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | attemptForeignWithdrawal          | 1367  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | deposit                           | 1354  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | donate                            | 1334  | 0       | 0        |\n|--------------+-----------------------------------+-------+---------+----------|\n| VaultHandler | withdraw                          | 1373  | 0       | 0        |\n╰--------------+-----------------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 51966\n  Bound result 529903\n  Bound result 4660\n  Bound result 226\n  Bound result 6232\n  Bound result 48\n  Bound result 2663327\n  Bound result 2046\n  Bound result 6947\n  Bound result 31449868\n  Bound result 12053\n  Bound result 244\n  Bound result 500\n  Bound result 1700000123\n  Bound result 31536000\n  Bound result 242\n  Bound result 999999999999999999999999\n  Bound result 2566\n  Bound result 1\n  Bound result 10456725624064639340\n  Bound result 120697189510742359104880\n  Bound result 27863288\n  Bound result 8230\n  Bound result 31450361\n  Bound result 1726784\n  Bound result 7753\n  Bound result 2558\n  Bound result 432886965720993657387185\n  Bound result 11598755\n  Bound result 242\n  Bound result 659918\n  Bound result 508\n  Bound result 218\n  Bound result 7\n  Bound result 7970660\n  Bound result 255\n  Bound result 903512210607953724342496\n  Bound result 17804353\n  Bound result 721702137350307661796063\n  Bound result 495790613315\n  Bound result 2293268\n  Bound result 2592\n  Bound result 200\n  Bound result 11\n  Bound result 1126808\n  Bound result 1514\n  Bound result 31506227\n  Bound result 8167\n  Bound result 8\n  Bound result 31451888\n  Bound result 60\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.94s (1.94s CPU time)\n\nRan 2 tests for test/SitOnHands.failure-invariant.t.sol:SitOnHandsFailureInvariantTest\n[PASS] invariant_FixedSupplyClaimsAndAllowancesMatchIndependentLedger() (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------------+-------------------------+-------+---------+----------╮\n| Contract              | Selector                | Calls | Reverts | Discards |\n+==============================================================================+\n| FixedInventoryHandler | advanceTime             | 2334  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | approve                 | 2229  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | deposit                 | 2190  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | donate                  | 2258  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | failedTokenDeposit      | 2219  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | failedTokenWithdrawal   | 2247  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | rejectForeignWithdrawal | 2168  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | rejectInvalidLock       | 2227  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | rejectUnknownWithdrawal | 2261  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | withdraw                | 2201  | 0       | 0        |\n|-----------------------+-------------------------+-------+---------+----------|\n| FixedInventoryHandler | withdrawAtBoundary      | 2242  | 0       | 0        |\n╰-----------------------+-------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000000000000\n  Bound result 1000\n  Bound result 86400\n  Bound result 1000000000000000000000000\n  Bound result 2000\n  Bound result 172800\n  Bound result 1000000000000000000000000\n  Bound result 3000\n  Bound result 259200\n  Bound result 10870\n  Bound result 20\n  Bound result 2458\n  Bound result 20\n  Bound result 1621\n  Bound result 31449856\n  Bound result 5779177\n  Bound result 5000\n  Bound result 31449959\n  Bound result 3171\n  Bound result 31449701\n  Bound result 80217511019955860902701\n  Bound result 3041954472\n  Bound result 2\n  Bound result 12579932\n  Bound result 11\n  Bound result 11\n  Bound result 99\n  Bound result 31449801\n  Bound result 88875462982487339704687\n  Bound result 17752471\n  Bound result 14600751959784666949673171903425108783356899519446919902052712\n  Bound result 86396\n  Bound result 659918\n  Bound result 659918\n  Bound result 3158\n  Bound result 11155111\n  Bound result 11331\n  Bound result 11331\n  Bound result 4893\n  Bound result 1548\n  Bound result 6478\n  Bound result 9399\n  Bound result 9172\n  Bound result 9172\n  Bound result 242\n  Bound result 8573\n  Bound result 538412904542007673208323\n  Bound result 8\n  Bound result 4202047189\n  Bound result 4202047189\n  Bound result 86400\n  Bound result 1000000\n  Bound result 230\n  Bound result 262371762367663075962879\n  Bound result 262371762367663075962879\n  Bound result 6\n  Bound result 31449607\n  Bound result 3600\n  Bound result 31454571\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913098104635\n  Bound result 51966\n  Bound result 240\n  Bound result 93740194065476114868303\n  Bound result 93740194065476114868303\n  Bound result 300\n  Bound result 10188\n  Bound result 10188\n  Bound result 200\n  Bound result 36870\n  Bound result 11\n  Bound result 60\n  Bound result 3776\n  Bound result 31460369\n  Bound result 259200\n  Bound result 14791\n  Bound result 14791\n  Bound result 169092974002443200607397\n  Bound result 31449845\n  Bound result 240\n  Bound result 31449605\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913098105567\n  Bound result 807350410627944553849998\n  Bound result 6581620\n  Bound result 9830\n  Bound result 12404\n  Bound result 7320106882057919544105751780457824641487468362785631778\n  Bound result 240\n  Bound result 240\n  Bound result 13135\n  Bound result 13135\n\n[PASS] testHandlerExercisesFailuresRecoveryAndRelocking() (gas: 6869043)\nLogs:\n  Bound result 1000000000000000000000000\n  Bound result 1000\n  Bound result 86400\n  Bound result 1000000000000000000000000\n  Bound result 2000\n  Bound result 172800\n  Bound result 1000000000000000000000000\n  Bound result 3000\n  Bound result 259200\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 100\n  Bound result 0\n  Bound result 100\n  Bound result 86400\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n  Bound result 0\n  Bound result 31536001\n  Bound result 2000\n  Bound result 2000\n  Bound result 31536000\n  Bound result 100\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.74s (7.74s CPU time)\n\nRan 5 test suites in 7.74s (9.89s CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":33,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SitOnHands.lock(uint256,uint256)\",\"SitOnHands.withdraw(uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":106,\"foundry.toml\":25,\"remappings.txt\":2,\"script/DeploySitOnHands.s.sol\":36,\"src/SitOnHands.sol\":106,\"test/DeploySitOnHands.t.sol\":97,\"test/SitOnHands.edges.t.sol\":251,\"test/SitOnHands.failure-invariant.t.sol\":330,\"test/SitOnHands.invariant.t.sol\":160,\"test/SitOnHands.t.sol\":447,\"test/TESTING.md\":49,\"test/mocks/MockIMD.sol\":92},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ee3aaab3d873471a95144b4062ecfe7ede318118abd0317c5d8be3907ac04c43","verifiedTreeHash":"c97c5fa4f5247bd4ef0990dd910e4e46fec6b8ef","verifierVersion":"0.1.0+94826a22"}]}