{"assessments":[],"deployments":[{"attestationHash":"95192fef25a74a6e3c650234230d5e1b8d6b718a5d561292482703b3bed6246f","chainId":1,"contracts":[],"id":"2285c0af-ec07-42cb-bd89-7637bc372d65","manifestHash":"81888fa9674de975241ad1c534167f1f86e216cd50a4a6112681d7ddb7ed3065","status":"parked"}],"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":"4d03bd5d-7a75-4b68-9de5-346a031940d6","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"f1dd6b1452c3fe6f19c8f33f9b5a34fcdff2356005edfd569aafd5d7080a238c","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":"a293d41111b38772e3c4b7cd3f2e277e9efad5096a4c603387ef20ae14e89664","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":"e2a4a9cfd8f5adaf53656978d4641936b63a6ba7ce0e67ea1f93c0307de0926d","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"dfdc3438a2b36ffa3c42842c22fdb00ac74150d7c0f6ca0b58edeca33b49323c","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":"dcfe7eb999647a7d5977b5cb1bb7721612147bf09c93dc3a69fe9c489fc05702","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":"562ffbd378d6026b153377b29026ef261d27c36998c1a7f67294f750fac0ce98","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"218854efc0141cceeba17f58b1083c4f29e96dd169af4faaa64b0a3293df0102","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"4d54da35257dd0a16cd9e51055e2f89d9ce2e2a46923d5323ec1a3e7fd564e2f","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"A contract where you send to it eth and you get it back on the same tx","parentJobId":null,"planHash":"631705b7c0941f7b6861d7f8be61c93f3723359cb02c47888efda652c1f92003","previousHash":"b7589207bcc5dbac8b70894da325b2a5c8a184a6df57094f76383e96f7c405cf","projectId":"4d03bd5d-7a75-4b68-9de5-346a031940d6","publication":{"commit":"b4dfbeab4ec85b1371d93e62de0dd71ae6618cac","deliveredAt":"2026-10-15T03:40:24.734Z","repoUrl":"https://github.com/identity-md-launches/launch-1807-contract-where-you-send"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"9f3242c41fa9a195de22cf114f0b3bd3bc2999b454e7c9c2d52fd3370870e03d","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"7724748bd1c941f46b0be276763c2b75ab41103b180e3488ef8820e1c6146bb1","device":"820ba900755d37ad","findings":[{"description":"EvmError: OutOfGas","line":null,"path":null,"reproduction":"The verifier deployed the manifest's contracts as the launch deploys them: from the launch factory (msg.sender), with IMD's launch wallet as tx.origin and $owner set, then read owner(), admin(), getOwner() and the admin role, and for a hook opened, seeded, traded and collected the pool from the factory (test/protected/evm_contracts/Owners.protected.t.sol in the protected floor).","severity":"high","title":"A launch contract is owned by the launch factory or by IMD"}],"hash":"218854efc0141cceeba17f58b1083c4f29e96dd169af4faaa64b0a3293df0102","nodeId":"111e3e10-4523-4295-891e-9225af049319","outcome":"completed","summary":"Created [launch.json](/home/imd/.identitymd/work/4d03bd5d-7a75-4b68-9de5-346a031940d6/111e3e10-4523-4295-891e-9225af049319/launch.json) listing only `EthRefund` with empty constructor arguments.\n\nManifest structure and compiled ABI checks passed. `forge build` succeeded with lint warnings; `forge test` passed all 22 tests. Only the manifest changed outside permitted scratch output.","treeHash":"3ef7dad6a1ff7ee2908653cf51f82c7e8b91abaf","usage":{"cachedInputTokens":135936,"inputTokens":28529,"model":"gpt-6-astra","outputTokens":1795,"runtime":"codex","turns":3,"wallClockMs":64256}},{"artifacts":[],"attempt":1,"bundleHash":"2331e8c39f6ad4f955a675113cc91a0a1ede1f7e522b1e4f143915009210d310","device":"1d2eacca0a6e8449","findings":[],"hash":"4d54da35257dd0a16cd9e51055e2f89d9ce2e2a46923d5323ec1a3e7fd564e2f","nodeId":"aa9c298f-8583-49e5-ac9c-26fe5c9040f2","outcome":"completed","summary":"Added nine tests covering same-transaction reuse, failure recovery, rollback, value boundaries, events, and forced ETH. Expanded invariants to 256 sequences and 16,384 calls.\n\n`forge build` and `forge test` pass with the required seed: 31 tests passed, zero failures.\n\nOnly `test/` files changed. No reproducible contract defects found.","treeHash":"161f120118d0caa961aec0370ddc27ce0494e80b","usage":{"cachedInputTokens":688640,"inputTokens":70379,"model":"gpt-6-astra","outputTokens":11267,"runtime":"codex","turns":6,"wallClockMs":336556}},{"artifacts":[],"attempt":1,"bundleHash":"aead33fb7044674d89a24401f118b190905b92f26358660ff0ee63b49ee03181","device":"29819a9ff9555c6f","findings":[],"hash":"562ffbd378d6026b153377b29026ef261d27c36998c1a7f67294f750fac0ce98","nodeId":"72c53113-ed76-4782-9061-330398e7e9f4","outcome":"completed","summary":"Implemented `EthRefund`: ETH returns to the caller in the same transaction; failed refunds revert. Includes reentrancy protection, tests, vendored dependencies, and deployment documentation.\n\nVerified with Solidity 0.8.26:\n\n- `forge build` passes.\n- Seeded `forge test` passes: 22 tests, including fuzz and invariants.\n- `forge fmt --check` passes.\n\nREADME documents gas costs, deployment assumptions, limitations, and two reviewed heuristic lint warnings.","treeHash":"144baf37d46d90d5aaa19f41706a0f869d7d9e34","usage":{"cachedInputTokens":311040,"inputTokens":49874,"model":"gpt-6-astra","outputTokens":15890,"runtime":"codex","turns":5,"wallClockMs":458204}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7ac9f7a7687c25e","findings":[{"citation":"resolved","description":"Not a security defect; funds are never at risk. The guard flag is a persistent storage bool that goes 0 -> 1 -> 0 on every successful call (SSTORE cold set 22,100 gas, then a reset that is only partially credited back by the EIP-3529 refund, which is capped at one fifth of the transaction's gas). The build pins evm_version = cancun, so TLOAD/TSTORE (EIP-1153) are available and give the same single-transaction guard semantics at about 100 gas per access. Because this contract's whole job is one call, the guard is roughly 40 percent of its execution gas. Reported as information for the author to decide on; the current guard is functionally correct, covers all three entry points, and is left in the correct state after every success and failure path (verified by reading _refund and by the existing caught/propagated reentry tests).","line":14,"path":"src/EthRefund.sol","reproduction":"Measured with a scratch Foundry test (test/scratch/Probe.t.sol, not kept): an EOA calls target.refund{value: 1 ether}() and the frame gas is measured with gasleft() before and after. Current contract (storage guard): 63,866 gas. Identical contract with a tload/tstore guard: 37,036 gas. Delta 26,830 gas per call before the EIP-3529 refund; on chain the refund credit is capped at 1/5 of total tx gas, so about 10k gas net per call is still lost. Expected by the author's design: the guard is a correctness measure; the gas cost is a design tradeoff, not a wrong output.","severity":"info","snippet":"    bool private refundInProgress;","title":"Reentrancy guard uses persistent storage, not transient storage, on a Cancun build: roughly 27k extra gas per refund before the end-of-tx refund credit"}],"hash":"a293d41111b38772e3c4b7cd3f2e277e9efad5096a4c603387ef20ae14e89664","nodeId":"3b7f849f-ee18-4d1e-8269-0d8a5f1e7810","outcome":"completed","summary":"The review is complete and the findings file is in place.\n\n**Outcome:** no exploitable defect in `src/EthRefund.sol`. The file `.imd-findings.json` holds one info-level note and seven coverage rows, all three listed entry points marked `holds`.\n\n**What I checked in my area (Execution Trace, Periphery, First Principles):**\n- Traced every path through the single private `_refund` body: the guard is set before the value call, zero value reverts, a failed refund reverts the whole payment, and only the current call's value ever leaves the contract. The pre-existing balance is unreachable by any path.\n- Cross-entry reentrancy (receive, fallback and refund in every combination) is rejected and the guard is always cleared afterwards, including when the receiver swallows the inner revert.\n- There are no parameters other than the sent value, no calldata decoding and no sentinel branches, so the parameter-divergence, encoding and stale-read failure modes have no surface here.\n- Scratch probes confirmed three edges the suite does not state explicitly: a caller still inside its constructor gets its refund, a call under roughly 31k gas reverts with nothing lost, and forge-std assertions are not stubbed.\n- Build, the full suite with the README seed and the format check all pass. The aderyn \"unchecked address\" lead is a false positive since the recipient is `msg.sender` by design.\n\n**The one note:** the guard is a persistent storage bool on a Cancun build. A measured refund costs about 64k frame gas versus 37k with a transient-storage guard. This is a design tradeoff, not a defect, and I reported it as info only.\n\n**Not reached:** nothing in the assigned area. There is no launch manifest yet and none is required at this stage.","treeHash":null,"usage":{"cachedInputTokens":548960,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":13952,"runtime":"claude","turns":23,"wallClockMs":229140}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"aac006f7fc669006","findings":[],"hash":"dcfe7eb999647a7d5977b5cb1bb7721612147bf09c93dc3a69fe9c489fc05702","nodeId":"6fe892fe-9c5e-49b6-8e79-0e66d675c6c7","outcome":"completed","summary":"I found no defects in `EthRefund`'s permissions, so the findings list is empty. The project's existing tests all pass (22 of 22, run with the README's fuzz seed), and I didn't change any project files.\n\n**Coverage:** all three entry points (`receive()`, `fallback()`, `refund()`) are marked `holds`, and I added three invariant rows.\n\n- **Access Control:** the contract has no roles and no initializer, and anyone may call it by design. The one guard (`nonReentrant`) runs on all three entry points through `_refund()`, so none of them has weaker protection. It can't be used to act for someone else: it only ever sends ETH to the address that paid it. It has no upgrade, `delegatecall` or `selfdestruct` path, and the deploying factory gets no powers.\n- **Asymmetry:** the three entry points run the same checks, change the same single stored value and make the same call. The success and failure cases match: either the refund and its event both happen, or everything is undone.\n- **Trust Gap:** there is no admin setting, fee, beneficiary or oracle, and payer and payee are always the same address. No one can move value from one user to another.\n- **Invariants checked:** the reentrancy lock can't stay set after a call or be held across transactions. ETH already sitting in the contract (for example, force-sent) is never paid out, since zero-value calls revert and nested calls can't reuse a payment. No privileged role exists.\n\n**Static analysis leads:**\n- I rejected aderyn's high-severity \"ETH sent without address checks\" warning. The recipient is always the caller who supplied the ETH and can't be chosen. A caller that rejects the refund only reverts its own payment, and existing tests cover this.\n- aderyn's low-severity \"modifier used only once\" warning is about style, not a defect.\n\n**Not covered:** gas cost is outside this area. The lock uses ordinary storage, where the newer transient storage would be cheaper. That costs more gas but isn't a defect, so I didn't report it. As always, a clean review doesn't prove there are no defects.\n\nThe coverage record and these notes are in `.imd-findings.json`, which is valid JSON.","treeHash":null,"usage":{"cachedInputTokens":379207,"inputTokens":16,"model":"claude-opus-5-5","outputTokens":5384,"runtime":"claude","turns":8,"wallClockMs":92985}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e02bec63aa9dcaa5","findings":[],"hash":"dfdc3438a2b36ffa3c42842c22fdb00ac74150d7c0f6ca0b58edeca33b49323c","nodeId":"0ffdc928-ac7a-43fc-b210-45bec1a70dcf","outcome":"completed","summary":"The review is complete. The findings file holds an empty findings list and a nine-row coverage record, and the scratch tests are removed.\n\n**Result: no defects found in the assigned area.** The contract has no arithmetic beyond forwarding `msg.value` verbatim, so the Math Precision and Numerical Gap checklists have nothing to bite on: no division, multiplication, subtraction, narrowing cast, unchecked block or fixed-point scale exists. The Boundary pass reduced to one external call and three payable entry points, and every corner case held.\n\nWhat I verified concretely, with the existing suite and my own scratch tests:\n\n- **All three entry points** route through the same guarded `_refund()`. Zero value reverts before any transfer. The refund is exactly `msg.value`, and the contract balance always covers it because value is credited before code runs.\n- **Receiver corner cases.** An EOA receives the ETH. A rejecting receiver, a receiver that runs out of gas, and a receiver that succeeds but starves the outer code of its remaining 1/64 gas all revert the whole payment with the sender's balance unchanged and the guard slot back to zero.\n- **Calldata corner cases.** One-byte calldata, eight-byte garbage, fuzzed bytes, and the `refund()` selector with trailing bytes all refund correctly. Nothing is ever decoded.\n- **Pre-existing balance** stays untouched across 4096 mixed invariant calls, and reentrancy during the callback is rejected on every entry point.\n- **Deployment.** No constructor, nonpayable creation, no owner, and no forbidden opcodes in the runtime.\n\nOne row is marked unreached: the protected ownership floor test imports a file absent from this tree, so it could not be run, though the contract exposes no owner and should trivially satisfy it. Static-analysis leads (unchecked address on the ETH send, single-use modifier) were examined and do not represent defects, since the recipient is `msg.sender` by design and the call result is checked.","treeHash":null,"usage":{"cachedInputTokens":648874,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":14859,"runtime":"claude","turns":23,"wallClockMs":236867}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d4b53d656a3fbe07","findings":[{"citation":"resolved","description":"Merged from audit_economics and audit_flow, which report the same root cause (one persistent-storage bool toggled 0->1->0 by the nonReentrant modifier at lines 16-21). Not a correctness or safety defect: the lock is acquired before the external call, held through the event, released on success and rolled back with the revert on every failure path, and it covers receive(), fallback() and refund() through _refund(). Reproduced by reading the code and by the cross-entry reentry probe described below. The only economic effect of this contract on a sender is transaction gas, and the SSTORE pair of a storage bool (cold 0->1 write of 22,100 gas plus a 1->0 reset that is only partly credited by the EIP-3529 refund, capped at one fifth of the transaction's gas) is the largest single component of that gas. foundry.toml pins evm_version = \"cancun\", where EIP-1153 TLOAD/TSTORE are available and give the same single-transaction lock semantics at about 100 gas per access. A tload/tstore lock (or OpenZeppelin ReentrancyGuardTransient) preserves the agreed design exactly: same entry points, same revert reasons, same event. The author may decide; this does not need to block release. The audit_flow report's slightly different figures (63,866 vs 37,036) come from a differently structured probe; the mechanism and order of magnitude are the same.","line":14,"path":"src/EthRefund.sol","reproduction":"Scratch test test/scratch/Probe.t.sol (not kept), forge 1.8.3, solc 0.8.26, cancun. State: fresh EthRefund and an otherwise identical copy whose nonReentrant modifier uses tload(0)/tstore(0,1)/tstore(0,0); EOA alice with 100 ether. Steps: alice calls refund{value: 1 ether}() twice on each contract, measuring gasleft() before and after each call. Actual, measured: storage lock 63,778 gas (first call) and 59,278 (second call); transient lock 41,595 and 37,095. Difference 22,183 gas per call, identical on every entry point since all three share _refund(). Expected for a lock that only needs to live for one transaction: on the order of 300 gas (one TLOAD plus two TSTOREs). Behavior, balances (alice ends with 100 ether, contract with 0), events and revert reasons are the same under both locks; only the gas differs.","severity":"info","snippet":"    bool private refundInProgress;","title":"Reentrancy lock uses persistent storage on a Cancun build; each refund costs about 22,000 gas more than a transient-storage lock"}],"hash":"e2a4a9cfd8f5adaf53656978d4641936b63a6ba7ce0e67ea1f93c0307de0926d","nodeId":"0abdecc0-c734-4faf-b9d5-47d3ff6f982d","outcome":"completed","summary":"The review is complete. The findings file is written and the tree has no other changes.\n\n**Outcome:** The EthRefund contract holds. No correctness or safety defect was found. The one finding kept is informational.\n\n**Specialist findings, settled**\n- audit_economics and audit_flow both reported the same root cause: the reentrancy lock is a persistent storage bool on a Cancun build where a transient-storage lock would do the same job for far less gas. I merged them into one info finding and reproduced it with a scratch test. My measurements matched the economics specialist's numbers exactly, and the flow specialist's figures differ only by probe structure. The guard itself is sound on every entry point and every failure path, so this stays at info and does not block release.\n- write_foundry_tests, audit_math and audit_permissions reported nothing, and my own pass agreed.\n- The aderyn high lead about unchecked recipient addresses does not reproduce. The recipient is always msg.sender, which cannot be the zero address.\n\n**My own pass**\n- All 31 project tests pass with the pinned fuzz seed, including the 256-run invariant suite. Formatting passes.\n- A cross-entry reentry probe confirmed that a callback into a different entry point mid-refund reverts with ReentrantCall and leaves balances untouched.\n- The manifest is schema-valid, matches the argument-less nonpayable constructor, and the runtime is 333 bytes with no forbidden opcodes. There is no owner, so nothing can be trapped at the factory.\n- Forge lint's reentrancy warnings are the documented sites that the lock covers.\n\n**Coverage:** all three entry points answered as holds, plus two invariant rows for conservation and deployment. The only thing left for the author to decide is whether to switch the lock to transient storage for the gas saving.","treeHash":null,"usage":{"cachedInputTokens":565724,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":7330,"runtime":"claude","turns":10,"wallClockMs":172497}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8e25456c4ae69acf","findings":[{"citation":"resolved","description":"Not a correctness or safety defect: the lock is acquired before the external call, released after the event, and rolls back with the call on every failure path, so the guard is sound on all three entry points. This is the only economic observation in the assigned area. The contract's sole economic effect on a sender is transaction gas (README: 'Transaction gas remains the sender's expense'), and most of that gas is the SSTORE pair of a storage bool: a cold 0->1 write (22,100) plus the 1->0 reset that is only partly refunded. foundry.toml pins evm_version = \"cancun\", where EIP-1153 transient storage (tstore/tload) is available and is the idiomatic home for a single-transaction lock; it also cannot be observed across transactions, which is the property the lock needs. Measured in a scratch test on this build: refund() with the current storage lock costs 63,778 gas on the first call and 59,278 on a later call from the same sender; an otherwise identical contract using tstore/tload for the lock costs 41,595 and 37,095. The ~22,183-gas difference is a per-call cost paid by every sender, with no change to behavior, events, or revert reasons. A fix (tstore/tload lock, or OpenZeppelin ReentrancyGuardTransient) preserves the agreed design; it is optional and does not need to block release.","line":16,"path":"src/EthRefund.sol","reproduction":"State: fresh EthRefund, EOA alice with 10 ether. Call alice -> EthRefund.refund{value: 1 ether}() and measure gasleft() before/after: 63,778 gas (storage lock). Same call against a copy of the contract whose nonReentrant modifier uses tload(0)/tstore(0,1)/tstore(0,0): 41,595 gas. Expected economics for a pure same-transaction refund: the lock should cost on the order of 300 gas (TLOAD 100 + 2x TSTORE 100). Actual: about 22,000 gas per call goes to the persistent-storage lock. Reproduced with forge test on test/scratch/Probe.t.sol::test_GasComparison (scratch, not kept).","severity":"info","snippet":"    modifier nonReentrant() {\n        if (refundInProgress) revert ReentrantCall();\n        refundInProgress = true;\n        _;\n        refundInProgress = false;\n    }","title":"Reentrancy lock lives in persistent storage; each refund pays ~22,000 gas more than a transient-storage lock on the pinned Cancun target"}],"hash":"f1dd6b1452c3fe6f19c8f33f9b5a34fcdff2356005edfd569aafd5d7080a238c","nodeId":"86d60e92-3694-4503-98be-df0614346c4d","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. No defects were found in the assigned economic area.\n\n**Outcome.** The contract is economically sound for its stated purpose. Every call returns exactly its own value to the immediate caller or reverts atomically, and no path pays out any pre-existing balance. One info-level note was recorded, and it is an optimization rather than a defect.\n\n**What I checked, against the Economic Security, Invariant and Flow Gap guides:**\n- Conservation and round trips. Contract balance is unchanged by every call. Forced ETH of 5 ether, planted with selfdestruct, stayed untouched under direct reentry, cross-contract reentry through a second sender, and zero-value calls.\n- Sender principal. Rejecting, non-payable and gas-exhausting receivers, a 2300-gas stipend send, uncaught reentry, and a 1 MiB return-data bomb all either revert atomically or leave the sender's balance unchanged. Only gas is spent, and only by that sender.\n- Lock state. The lock is set before the external call and cleared after the event. Every failure path reverts and rolls the write back, so no sequence strands it.\n- Griefing across senders. There is no shared state beyond the lock, no queue, fee, owner or balance accounting, so one sender's callback can only revert its own call.\n- Entry-point equivalence. All three entry points reach the same private function. Odd calldata (1 byte, 3 bytes, selector plus extra bytes) and a max uint256 value all refund cleanly.\n- Static-analysis leads. The aderyn \"unchecked address\" lead is by design, since the recipient is msg.sender and cannot be zero for an executing call. Not a defect.\n\n**Info note recorded.** The reentrancy lock lives in persistent storage. On the pinned Cancun target, a transient-storage lock would do the same job for far less gas, and gas is the only economic cost this design imposes on senders.\n\n| Lock type | First refund() | Later refund() |\n| --- | --- | --- |\n| Storage bool (current) | 63,778 gas | 59,278 gas |\n| Transient storage | 41,595 gas | 37,095 gas |\n\n**Coverage.** All three listed entry points are marked `holds`, with seven extra rows for the invariants and leads above. The existing 22 project tests pass with the pinned fuzz seed. My scratch probe tests were removed and no project file was changed.","treeHash":null,"usage":{"cachedInputTokens":782430,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":14576,"runtime":"claude","turns":17,"wallClockMs":216330}}],"verification":[{"checks":[{"durationMs":1056,"exitCode":0,"name":"build","output":"Compiling 23 files with Solc 0.8.26\nSolc 0.8.26 finished in 983.92ms\nCompiler run successful!\n","passed":true},{"durationMs":455,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 21 tests for test/EthRefund.t.sol:EthRefundTest\n[PASS] testFuzz_ArbitraryCalldataRefunds(uint96,bytes) (runs: 1024, μ: 65220, ~: 65051)\nLogs:\n  Bound result 2904\n\n[PASS] testFuzz_CaughtReentryCannotDrainBalance(uint8,uint8,bool) (runs: 1024, μ: 394245, ~: 390311)\n[PASS] testFuzz_ExactRefundPreservesExistingBalance(uint96,uint96,uint8) (runs: 1024, μ: 174073, ~: 174158)\nLogs:\n  Bound result 10615\n\n[PASS] test_CallerWithoutPayableReceiverCannotLoseItsPayment() (gas: 221666)\n[PASS] test_ContractReceivesRefundBeforeCallReturns() (gas: 190385)\n[PASS] test_DeploymentRejectsEth() (gas: 95766)\n[PASS] test_ExplicitRefundReturnsFullValue() (gas: 59894)\n[PASS] test_FactoryCanDeployWithoutArgumentsOrExternalDependencies() (gas: 245916)\n[PASS] test_FailedRefundDoesNotLockContractOrBlockOtherSenders() (gas: 345654)\n[PASS] test_OneWeiIsRefunded() (gas: 59915)\n[PASS] test_PlainEthTransferReturnsFullValueAndEmitsEvent() (gas: 62565)\n[PASS] test_PreexistingBalanceCannotBeClaimedWithZeroValue() (gas: 61113)\n[PASS] test_ReceiverExhaustingGasRollsBackPayment() (gas: 1218259)\n[PASS] test_RefundGoesToImmediateCallerInsteadOfTransactionOrigin() (gas: 154661)\n[PASS] test_RejectingReceiverRevertsPaymentOnEveryEntryPoint() (gas: 270341)\n[PASS] test_RuntimeFitsDeploymentPolicy() (gas: 194534)\n[PASS] test_SequentialCallsCanUseEveryEntryPoint() (gas: 302222)\n[PASS] test_StipendLimitedSendFailsWithoutLosingValue() (gas: 44823)\n[PASS] test_UncaughtReentryRevertsEntirePayment() (gas: 315446)\n[PASS] test_UnrecognizedCalldataAlsoRefunds() (gas: 60191)\n[PASS] test_ZeroValueRevertsOnEveryEntryPointWithoutCallback() (gas: 175194)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 25.96ms (76.73ms CPU time)\n\nRan 1 test for test/EthRefund.invariant.t.sol:EthRefundInvariantTest\n[PASS]\nEthRefundInvariantTest invariants:\n[PASS] invariant_EachSuccessfulPaymentIsRefundedExactlyOnce\n[PASS] invariant_NoSenderLosesPrincipalAndPreexistingEthIsUntouched\n EthRefundInvariantTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭---------------+--------------+-------+---------+----------╮\n| Contract      | Selector     | Calls | Reverts | Discards |\n+===========================================================+\n| RefundHandler | pay          | 1073  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | reenter      | 1019  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | rejectRefund | 998   | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | zeroPayment  | 1006  | 0       | 0        |\n╰---------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 5461\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 3333778057954456161\n  Bound result 724\n  Bound result 930\n  Bound result 2552851539\n  Bound result 976453619\n  Bound result 14264337593543956519\n  Bound result 14264337593543950335\n  Bound result 4418\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 496\n  Bound result 4696\n  Bound result 84456766042083259900\n  Bound result 207472333\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 379.50ms (378.41ms CPU time)\n\nRan 2 test suites in 380.55ms (405.46ms CPU time): 22 tests passed, 0 failed, 0 skipped (22 total tests)\n","passed":true},{"durationMs":35,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EthRefund.fallback()\",\"EthRefund.receive()\",\"EthRefund.refund()\"],\"files\":{\".gitignore\":3,\"README.md\":115,\"foundry.toml\":22,\"launch.json\":10,\"src/EthRefund.sol\":48,\"test/EthRefund.invariant.t.sol\":116,\"test/EthRefund.t.sol\":290,\"test/helpers/RefundCaller.sol\":79},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed; 1 finding(s) for the step that wrote the contracts","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"218854efc0141cceeba17f58b1083c4f29e96dd169af4faaa64b0a3293df0102","verifiedTreeHash":"3ef7dad6a1ff7ee2908653cf51f82c7e8b91abaf","verifierVersion":"0.1.0+820e5ed7"},{"checks":[{"durationMs":1532,"exitCode":0,"name":"build","output":"Compiling 25 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.43s\nCompiler run successful with warnings:\nWarning (3628): This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.\n  --> test/helpers/RefundAdversaries.sol:74:1:\n   |\n74 | contract FallbackRefundCaller {\n   | ^ (Relevant source part starts here and spans across multiple lines).\nNote: The payable fallback function is defined here.\n  --> test/helpers/RefundAdversaries.sol:87:5:\n   |\n87 |     fallback(bytes calldata data) external payable returns (bytes memory) {\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/RefundAdversaries.sol:69:9:\n   |\n69 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\n","passed":true},{"durationMs":1849,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 9 tests for test/EthRefund.atomic.t.sol:EthRefundAtomicTest\n[PASS] testFuzz_FailedCallCanBeCaughtAndRetriedWithinOneTransaction(uint96,uint8,uint8,uint8) (runs: 1024, μ: 306783, ~: 278551)\nLogs:\n  Bound result 4\n\n[PASS] testFuzz_FullWidthPaymentsPreserveUnrelatedBalance(uint256,uint256,uint8) (runs: 1024, μ: 830097, ~: 830089)\nLogs:\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639934\n  Bound result 1\n\n[PASS] testFuzz_RefundsAreImmediatelyReusable(uint96,uint8,bytes32) (runs: 1024, μ: 367091, ~: 325556)\nLogs:\n  Bound result 79228162514264337593543950335\n  Bound result 2\n\n[PASS] test_ActualForcedEthCannotBeClaimedByZeroOrPositivePayment() (gas: 545550)\n[PASS] test_EnclosingRevertRollsBackCompletedRefundAndCallbackState() (gas: 549503)\n[PASS] test_MaximumUint256PaymentIsReturnedExactly() (gas: 823503)\n[PASS] test_OneWeiRefundWhenTemporaryTargetBalanceReachesUint256Maximum() (gas: 823171)\n[PASS] test_PayableFallbackReceiverAcceptsRefundDespiteReturningFalseData() (gas: 630133)\n[PASS] test_SameWeiCanBeReusedAcrossAllCalldataShapesThenSpentInOneTransaction() (gas: 424900)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 53.55ms (154.31ms CPU time)\n\nRan 21 tests for test/EthRefund.t.sol:EthRefundTest\n[PASS] testFuzz_ArbitraryCalldataRefunds(uint96,bytes) (runs: 1024, μ: 65225, ~: 65064)\nLogs:\n  Bound result 51585204113284\n\n[PASS] testFuzz_CaughtReentryCannotDrainBalance(uint8,uint8,bool) (runs: 1024, μ: 394255, ~: 390311)\n[PASS] testFuzz_ExactRefundPreservesExistingBalance(uint96,uint96,uint8) (runs: 1024, μ: 174070, ~: 174158)\nLogs:\n  Bound result 79228162514264337593543950334\n\n[PASS] test_CallerWithoutPayableReceiverCannotLoseItsPayment() (gas: 221666)\n[PASS] test_ContractReceivesRefundBeforeCallReturns() (gas: 190385)\n[PASS] test_DeploymentRejectsEth() (gas: 95766)\n[PASS] test_ExplicitRefundReturnsFullValue() (gas: 59894)\n[PASS] test_FactoryCanDeployWithoutArgumentsOrExternalDependencies() (gas: 245916)\n[PASS] test_FailedRefundDoesNotLockContractOrBlockOtherSenders() (gas: 345654)\n[PASS] test_OneWeiIsRefunded() (gas: 59915)\n[PASS] test_PlainEthTransferReturnsFullValueAndEmitsEvent() (gas: 62565)\n[PASS] test_PreexistingBalanceCannotBeClaimedWithZeroValue() (gas: 61113)\n[PASS] test_ReceiverExhaustingGasRollsBackPayment() (gas: 1218259)\n[PASS] test_RefundGoesToImmediateCallerInsteadOfTransactionOrigin() (gas: 154661)\n[PASS] test_RejectingReceiverRevertsPaymentOnEveryEntryPoint() (gas: 270341)\n[PASS] test_RuntimeFitsDeploymentPolicy() (gas: 194534)\n[PASS] test_SequentialCallsCanUseEveryEntryPoint() (gas: 302222)\n[PASS] test_StipendLimitedSendFailsWithoutLosingValue() (gas: 44823)\n[PASS] test_UncaughtReentryRevertsEntirePayment() (gas: 315446)\n[PASS] test_UnrecognizedCalldataAlsoRefunds() (gas: 60191)\n[PASS] test_ZeroValueRevertsOnEveryEntryPointWithoutCallback() (gas: 175194)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 53.56ms (158.30ms CPU time)\n\nRan 1 test for test/EthRefund.invariant.t.sol:EthRefundInvariantTest\n[PASS]\nEthRefundInvariantTest invariants:\n[PASS] invariant_EachSuccessfulPaymentIsRefundedExactlyOnce\n[PASS] invariant_NoSenderLosesPrincipalAndPreexistingEthIsUntouched\n EthRefundInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------+--------------+-------+---------+----------╮\n| Contract      | Selector     | Calls | Reverts | Discards |\n+===========================================================+\n| RefundHandler | forceEth     | 2760  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | pay          | 2742  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | payBatch     | 2669  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | reenter      | 2771  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | rejectRefund | 2696  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | zeroPayment  | 2746  | 0       | 0        |\n╰---------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 24719334441383607081\n  Bound result 3270\n  Bound result 2\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 211801474\n  Bound result 3\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 7833\n  Bound result 8486\n  Bound result 1695169583271317\n  Bound result 7\n  Bound result 3940\n  Bound result 444274\n  Bound result 14264337593543950335\n  Bound result 2592286112\n  Bound result 9040\n  Bound result 91494489962425604564\n  Bound result 32404007\n  Bound result 4485612279522923\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 2442\n  Bound result 7561\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 3\n  Bound result 4546\n  Bound result 14264337593543950335\n  Bound result 26979368134100058899\n  Bound result 4\n  Bound result 3324\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 832\n  Bound result 3868\n  Bound result 21630003679868\n  Bound result 8766\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 5634\n  Bound result 1508887317\n  Bound result 349509391443746375\n  Bound result 8323\n  Bound result 2314\n  Bound result 198504710854437764\n  Bound result 25295967\n  Bound result 14264337593543950335\n  Bound result 193\n  Bound result 14264337593543950335\n  Bound result 2616\n  Bound result 2672\n  Bound result 14264337593543950335\n  Bound result 42331360609703021499\n  Bound result 6617\n  Bound result 14264337593543950335\n  Bound result 3\n  Bound result 14264337593543950335\n  Bound result 27480413260370\n  Bound result 2\n  Bound result 32418901452414216849\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.76s (1.75s CPU time)\n\nRan 3 test suites in 1.76s (1.86s CPU time): 31 tests passed, 0 failed, 0 skipped (31 total tests)\n","passed":true},{"durationMs":39,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EthRefund.fallback()\",\"EthRefund.receive()\",\"EthRefund.refund()\"],\"files\":{\".gitignore\":3,\"README.md\":115,\"foundry.toml\":22,\"src/EthRefund.sol\":48,\"test/EthRefund.atomic.t.sol\":209,\"test/EthRefund.invariant.t.sol\":164,\"test/EthRefund.t.sol\":290,\"test/helpers/RefundAdversaries.sol\":95,\"test/helpers/RefundCaller.sol\":79},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4d54da35257dd0a16cd9e51055e2f89d9ce2e2a46923d5323ec1a3e7fd564e2f","verifiedTreeHash":"161f120118d0caa961aec0370ddc27ce0494e80b","verifierVersion":"0.1.0+820e5ed7"},{"checks":[{"durationMs":1076,"exitCode":0,"name":"build","output":"Compiling 23 files with Solc 0.8.26\nSolc 0.8.26 finished in 987.40ms\nCompiler run successful!\n","passed":true},{"durationMs":508,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 21 tests for test/EthRefund.t.sol:EthRefundTest\n[PASS] testFuzz_ArbitraryCalldataRefunds(uint96,bytes) (runs: 1024, μ: 65220, ~: 65051)\nLogs:\n  Bound result 657\n\n[PASS] testFuzz_CaughtReentryCannotDrainBalance(uint8,uint8,bool) (runs: 1024, μ: 394245, ~: 390311)\n[PASS] testFuzz_ExactRefundPreservesExistingBalance(uint96,uint96,uint8) (runs: 1024, μ: 174073, ~: 174158)\nLogs:\n  Bound result 79228162514264337593543950333\n\n[PASS] test_CallerWithoutPayableReceiverCannotLoseItsPayment() (gas: 221666)\n[PASS] test_ContractReceivesRefundBeforeCallReturns() (gas: 190385)\n[PASS] test_DeploymentRejectsEth() (gas: 95766)\n[PASS] test_ExplicitRefundReturnsFullValue() (gas: 59894)\n[PASS] test_FactoryCanDeployWithoutArgumentsOrExternalDependencies() (gas: 245916)\n[PASS] test_FailedRefundDoesNotLockContractOrBlockOtherSenders() (gas: 345654)\n[PASS] test_OneWeiIsRefunded() (gas: 59915)\n[PASS] test_PlainEthTransferReturnsFullValueAndEmitsEvent() (gas: 62565)\n[PASS] test_PreexistingBalanceCannotBeClaimedWithZeroValue() (gas: 61113)\n[PASS] test_ReceiverExhaustingGasRollsBackPayment() (gas: 1218259)\n[PASS] test_RefundGoesToImmediateCallerInsteadOfTransactionOrigin() (gas: 154661)\n[PASS] test_RejectingReceiverRevertsPaymentOnEveryEntryPoint() (gas: 270341)\n[PASS] test_RuntimeFitsDeploymentPolicy() (gas: 194534)\n[PASS] test_SequentialCallsCanUseEveryEntryPoint() (gas: 302222)\n[PASS] test_StipendLimitedSendFailsWithoutLosingValue() (gas: 44823)\n[PASS] test_UncaughtReentryRevertsEntirePayment() (gas: 315446)\n[PASS] test_UnrecognizedCalldataAlsoRefunds() (gas: 60191)\n[PASS] test_ZeroValueRevertsOnEveryEntryPointWithoutCallback() (gas: 175194)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 29.47ms (81.93ms CPU time)\n\nRan 1 test for test/EthRefund.invariant.t.sol:EthRefundInvariantTest\n[PASS]\nEthRefundInvariantTest invariants:\n[PASS] invariant_EachSuccessfulPaymentIsRefundedExactlyOnce\n[PASS] invariant_NoSenderLosesPrincipalAndPreexistingEthIsUntouched\n EthRefundInvariantTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭---------------+--------------+-------+---------+----------╮\n| Contract      | Selector     | Calls | Reverts | Discards |\n+===========================================================+\n| RefundHandler | pay          | 1073  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | reenter      | 1019  | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | rejectRefund | 998   | 0       | 0        |\n|---------------+--------------+-------+---------+----------|\n| RefundHandler | zeroPayment  | 1006  | 0       | 0        |\n╰---------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 5461\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 3333778057954456161\n  Bound result 724\n  Bound result 930\n  Bound result 2552851539\n  Bound result 976453619\n  Bound result 28528675187087906854\n  Bound result 14264337593543950335\n  Bound result 4418\n  Bound result 14264337593543950335\n  Bound result 14264337593543950335\n  Bound result 497\n  Bound result 4698\n  Bound result 84456766042083259900\n  Bound result 207472333\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 427.22ms (425.45ms CPU time)\n\nRan 2 test suites in 428.78ms (456.69ms CPU time): 22 tests passed, 0 failed, 0 skipped (22 total tests)\n","passed":true},{"durationMs":39,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EthRefund.fallback()\",\"EthRefund.receive()\",\"EthRefund.refund()\"],\"files\":{\".gitignore\":3,\"README.md\":115,\"foundry.toml\":22,\"src/EthRefund.sol\":48,\"test/EthRefund.invariant.t.sol\":116,\"test/EthRefund.t.sol\":290,\"test/helpers/RefundCaller.sol\":79},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":329,"exitCode":0,"name":"slither","output":"slither: no results at low impact or above","passed":true},{"durationMs":207,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/EthRefund.sol:24: ETH transferred without address checks (3 places)\n[low] modifier-used-only-once at src/EthRefund.sol:16: Modifier Invoked Only Once","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"562ffbd378d6026b153377b29026ef261d27c36998c1a7f67294f750fac0ce98","verifiedTreeHash":"144baf37d46d90d5aaa19f41706a0f869d7d9e34","verifierVersion":"0.1.0+820e5ed7"}]}