{"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":"e568de57-17f3-4cb0-b05f-37bc363e32df","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"26fd87f23e975310ba3ac738842f92dfddef007a3364758cdd3a56ccb178cd99","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":"9f00932fa03605d40875641c8d9b90e27cd8eb2098d762c6c56fd37f34f69f43","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":"fdae59b6de4663929dd1e1de6e49b1eff0686a00ec771cf36fd5c89235035b29","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":"fafcc46fc11ff590f561203f1f7a35da0a8da465be685dfb6e3bb50a877f9f38","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":"9067aa428bdf2ae393645400843687515a7592f5dbb8d78b7ff92b3a1fd57b91","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":"912dca51ae4075ef8183a3933452f017fe9cddd70656231a8972289623688df2","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"ba2f21bec0e06a3627aff7585f86b1e1a76f3528867532064617bf4d1784fbc4","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"a4652a25e78e245c2198ee286fb0d41df72074510daba0f649bca64738ad520f","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":"Build and deploy GameEscrow, version 2 of NIGHTSHIFT's PinballEscrow (live at 0x908da27d2caa584133dfb41fec9d6362dca6bcdd), for player-versus-player skill games (Space Pinball, NIGHT CHESS and later games). Two players stake the same amount of the same token; the winner, as decided by the NIGHTSHIFT server's verified result, takes the pot minus a 5% fee. The only change from v1: players choose the amount.\n\nTokens: only IMD (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7) and $SHIFT (0xe5e58e53d0bb204aa62c995a6c8cc8a48e60c3ac), both plain ERC-20 with 18 decimals and no transfer fee. Any stake between a per-token minimum and maximum is allowed. Initial limits, set in the constructor: IMD min 0.5, max 1000; SHIFT min 10000, max 50000000 (whole tokens). The owner may change a token's min and max with setLimits(token, min, max) (event LimitsSet), but never above a hard cap fixed in the code (IMD 10000, SHIFT 1000000000) and never with min > max; changing limits never affects matches already opened.\n\nFlow:\n1. open(bytes32 matchId, address token, uint256 stake, bytes32 game): player A deposits the stake (transferFrom, exact amount, within the limits). game is a free label (e.g. \"pinball\", \"chess\"). Emits Opened(matchId, token, stake, game, playerA). matchId must be new.\n2. join(bytes32 matchId): player B deposits the same token and exact stake. Emits Joined(matchId, playerB). Exactly two players; A cannot join her own match.\n3. settle(bytes32 matchId, address winner, bool draw, uint256 deadline, bytes signature): anyone may submit the result signed by the result signer (EIP-712 typed data: domain name \"NightshiftGames\", version \"2\", chainId, this contract; struct Result(bytes32 matchId, address winner, bool draw, uint256 deadline)). The signature must be valid, not expired, for a joined, unsettled match, and winner must be one of the two players unless draw. Winner gets 95% of the pot; 5% goes to the fee splitter 0x1119d5df2f046bc2d3c0f6279ef2e9ce49422d44. A draw refunds both players in full. Emits Settled(matchId, winner, draw, payout, fee).\n4. refund(bytes32 matchId): if a match is not joined within 1 hour of open, A gets her stake back; if a joined match is not settled within 24 hours, both get their stake back. Emits Refunded(matchId).\n5. cancel(bytes32 matchId): player A may cancel her own match before anyone joins and get her stake back at once. Emits Cancelled(matchId).\n6. Views: matches(matchId) returning token, stake, game, playerA, playerB, openedAt, joinedAt, settled; limits(token) returning min and max.\n\nOwner: 0x6c4c78f2c3bdf4c4318fe305fb1c8bc1694d19b5. The owner can only change the result signer (setSigner, event), the limits within the hard caps (setLimits, event), and pause new opens and joins (pause/unpause; settle, refund and cancel keep working while paused). The owner can never move, take or redirect any stake, change the 5% fee, the fee recipient or the tokens. No upgradeability, no delegatecall, no selfdestruct. Reentrancy-safe (checks-effects-interactions plus a guard), SafeERC20, every state change emits an event. Initial result signer: a constructor argument equal to $owner.\n\nTests (Foundry): full flows for both tokens with amounts at and between the limits, below/above limits refused, draw, both refunds, cancel, wrong signer, expired and replayed signatures, signature for the wrong contract or chain refused, winner not a player refused, double settle, joining your own match, wrong stake, paused opens/joins, fee exactly 5% to the splitter (including odd amounts and rounding), setLimits bounds and hard caps, fuzz on stakes and call orders.","parentJobId":null,"planHash":"18739431ff10319d4661bf33530e16014fdca14e1cbabfd7c7e93931834d0105","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e568de57-17f3-4cb0-b05f-37bc363e32df","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2201-build-deploy-gameescrow-version"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50981","feedbackHash":"99c3d9b4a18c0de080fcdf8fb6cba95c681de4e0a17a0b1697d400ee7999a2f3","nodeKey":"audit_economics","submissionHash":"26fd87f23e975310ba3ac738842f92dfddef007a3364758cdd3a56ccb178cd99","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51285","feedbackHash":"19cbd7e0db99b407c5c9fc6514776a6e54645e9734a657af00c96f852b0a4928","nodeKey":"audit_flow","submissionHash":"9f00932fa03605d40875641c8d9b90e27cd8eb2098d762c6c56fd37f34f69f43","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51083","feedbackHash":"2ceb99d2c46f64ed301782acfc9b1f1857f0385f794f520b5ce06b4e0f9daa40","nodeKey":"audit_judge","submissionHash":"fdae59b6de4663929dd1e1de6e49b1eff0686a00ec771cf36fd5c89235035b29","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52251","feedbackHash":"8e547fe856c6a1facb7c72ceb24b2a5cf26335ece6879bd9d2a5735779930bc3","nodeKey":"audit_math","submissionHash":"fafcc46fc11ff590f561203f1f7a35da0a8da465be685dfb6e3bb50a877f9f38","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52120","feedbackHash":"a6e42dc571bdc60853664acc39cb337447f979e9845a1826c1146e0de8f5fc00","nodeKey":"audit_permissions","submissionHash":"9067aa428bdf2ae393645400843687515a7592f5dbb8d78b7ff92b3a1fd57b91","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51495","feedbackHash":"e5693b70aa564702b926c7760973d7674a9b98d49da2ced354a763cf8bd1319b","nodeKey":"build_contract_project","submissionHash":"912dca51ae4075ef8183a3933452f017fe9cddd70656231a8972289623688df2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51737","feedbackHash":"79584647fbaf2ce28cc76e187a9536ff6ac50806aa7b1755c750f81aa1ca527f","nodeKey":"manifest","submissionHash":"ba2f21bec0e06a3627aff7585f86b1e1a76f3528867532064617bf4d1784fbc4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52569","feedbackHash":"a0749a6e146bddc166a335b7f9d0884ea6213a410d16cceb75ae64425d76b706","nodeKey":"write_foundry_tests","submissionHash":"a4652a25e78e245c2198ee286fb0d41df72074510daba0f649bca64738ad520f","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"3b319ace72c40f72d0d892815b9b96dfc0d163325a7e552717c01ebccb784a70","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5739ce0d803a43cd","findings":[{"citation":"resolved","description":"matchId is chosen by the caller and is a permanent nonce (line 13: 'Match IDs are permanent nonces, including after cancellation or refund'). cancel() (line 175-183) refunds player A at once with no waiting period and no fee, so a griefer who sees Alice's pending open(matchId, ...) in the public mainnet mempool (launch chain is Ethereum, chain ID 1) can front-run it with a contract that calls open(matchId, IMD, 0.5 ether, game) then cancel(matchId) in one transaction. The griefer's stake comes straight back; the only cost is gas. Alice's transaction then reverts with MatchAlreadyExists and the matchId is unusable forever, so a session the NIGHTSHIFT server has already bound to that id must be re-issued. Repeated against one player this blocks them from opening any match whose id is visible before inclusion. No funds are lost; this is a targeted denial of service inherent to caller-chosen ids plus free instant cancellation. Mitigation that keeps the spec's flow: derive the stored key from the caller as well as the supplied id (e.g. keccak256(abi.encodePacked(msg.sender, matchId))) so only Alice can occupy Alice's id, or have the frontend submit ids that are not shared before inclusion; INTEGRATION.md already asks for unpredictable ids, which is a client-side control the contract does not enforce.","line":97,"path":"src/GameEscrow.sol","reproduction":"State: fresh GameEscrow, Alice and Griefer both hold >= 0.5 IMD and approved the escrow. 1) Alice broadcasts open(ID, IMD, 100e18, 'pinball'). 2) Griefer, seeing it in the mempool, sends with higher priority a contract call that does open(ID, IMD, 0.5e18, 'x') followed by cancel(ID): Griefer's 0.5 IMD returns to Griefer in the same tx, matches(ID).settled == true, matches(ID).playerA == Griefer. 3) Alice's open(ID, ...) executes next and reverts MatchAlreadyExists (line 97); open(ID, ...) by anyone reverts forever (the existing test test_MatchIdAndLabelMayBeZeroButIdNeverReused in test/GameEscrowFlows.t.sol lines 228-239 shows exactly that re-open after cancel reverts). Expected: Alice's chosen id is usable by Alice. Actual: id permanently burned for the cost of one transaction's gas.","severity":"low","snippet":"        if (matches[matchId].playerA != address(0)) revert MatchAlreadyExists();","title":"Match ID squatting: any observer can burn a victim's chosen matchId at gas-only cost by front-running open and cancelling in the same transaction"},{"citation":"resolved","description":"This follows the brief's struct exactly (Result(bytes32 matchId,address winner,bool draw,uint256 deadline)) and is reported as a trust seam, not a code defect. join() is permissionless and the contract has no reserved opponent, so the on-chain playerB can differ from the opponent the server matched off-chain. The contract only checks that the signed winner is one of the two recorded players. If the server signs 'matchId M, winner Alice' based on its own lobby (Alice vs Bob) while on-chain Carol front-ran Bob's join, Carol loses her stake to Alice without having played her, and 5% goes to the splitter. Conversely a result pre-signed before join (winner = playerA) makes whoever joins lose instantly. docs/INTEGRATION.md documents that the backend must read the on-chain match snapshot and reject unexpected participants before signing and must never pre-sign. The contract cannot enforce that. Changing the struct to include playerA, playerB, token and stake would close the seam on-chain but changes the agreed signing schema, so it is a scope decision for the requester; otherwise the server-side checks in INTEGRATION.md are load-bearing for fund safety.","line":138,"path":"src/GameEscrow.sol","reproduction":"State: Alice opens M with 100 IMD intending to play Bob; the server records lobby M = (Alice, Bob). Carol calls join(M) before Bob (Bob's join then reverts AlreadyJoined). Server, without re-reading matches(M).playerB, signs Result(M, Alice, false, deadline). Anyone calls settle(M, Alice, false, deadline, sig): the check on line 138 passes because Alice is playerA, the signature verifies on line 139, Alice receives 190 IMD and the splitter 10 IMD, Carol's 100 IMD is gone. Expected under the product's intent: a result applies only to the match the server actually adjudicated between the recorded players. Actual: the contract accepts any signer result whose winner is a recorded player; participant binding is entirely off-chain.","severity":"info","snippet":"        if (!draw && winner != gameMatch.playerA && winner != gameMatch.playerB) revert InvalidWinner();\n        if (ECDSA.recover(hashResult(matchId, winner, draw, deadline), signature) != signer) revert InvalidSignature();","title":"Signed Result binds only matchId/winner/draw/deadline, not the players, token or stake, so a result signed for the intended lobby can pay out against a different on-chain opponent"}],"hash":"26fd87f23e975310ba3ac738842f92dfddef007a3364758cdd3a56ccb178cd99","nodeId":"f4cf12cf-e30a-4d97-b030-bec187505d28","outcome":"completed","summary":"The working tree shows no tracked changes. The findings file and the scratch fork test are the only additions, and both paths are the ones the review was told to use. The deletion of the scratch directory was refused by the sandbox, which is fine since nothing under test/scratch/ is kept.\n\n**Outcome:** no critical, high or medium defects in the Economic Security, Invariant or Flow Gap areas. The findings file holds one low and one informational finding plus coverage rows for all nine entry points and five invariants.\n\n**What I checked**\n\n- **Value conservation.** Every inflow and outflow is exactly the stored stake, or the pot split into payout plus fee. The fee is floor(pot/20) with the dust going to the winner, and a draw refunds both players with zero fee. The project's stateful invariant suite and all 70 tests pass.\n- **Timing windows.** Join, settle and refund are mutually exclusive at the exact boundaries (1 hour after open, 24 hours after join). There is no state where a loser can block or race a settlement, and no state where a stake can be stranded, including while paused.\n- **Live dependencies on Ethereum mainnet.** IMD is a LayerZero OFT and SHIFT a minimal ERC-20. Neither sits behind a proxy and neither exposes pause or blacklist functions. A scratch fork test ran open, join, decisive settle and draw through the real token bytecode at minimum, odd and maximum stakes, and every balance delta was exact. The splitter is the same address v1 already pays fees to. The owner address is an EIP-7702 delegated EOA, so it can still produce the ECDSA signatures that settlement requires.\n- **Owner powers.** Limited to signer rotation, limits within the hard caps, and pausing new deposits. Fee, recipient, tokens and owner are constants.\n\n**Findings written**\n\n1. **Low, match ID squatting.** Because cancel refunds instantly at no cost and match IDs are permanent, anyone watching the public mempool can front-run a player's open with the same ID, cancel in the same transaction, and burn that ID forever for the price of gas. No funds are lost. A mitigation that keeps the specified flow is to key the match on the caller as well as the supplied ID.\n2. **Info, trust seam in the signed result.** The Result struct binds neither the players nor the token or stake, exactly as the brief specifies. A result signed for the intended lobby still settles if a different opponent front-ran the join, so the server-side participant checks in docs/INTEGRATION.md are load-bearing for fund safety. Closing it on-chain would change the agreed signing schema, so it is flagged as a scope decision rather than a defect.\n\n**Not reached:** nothing within the assigned area. The static-analysis leads (reentrancy on the balance delta, uninitialized local, timestamp comparisons) were traced and are false positives for these plain tokens under the reentrancy guard.","treeHash":null,"usage":{"cachedInputTokens":1087616,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":30574,"runtime":"claude","turns":32,"wallClockMs":504136}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a5c5e95a2ed07117","findings":[{"citation":"resolved","description":"Branch asymmetry inside settle(): the decisive branch validates that `winner` is player A or B, the draw branch validates nothing about `winner`, yet both branches emit the same `Settled(matchId, winner, draw, payout, fee)` event with `winner` as an indexed topic (line 146: `emit Settled(matchId, winner, true, stake, 0);`). On-chain transfers are unaffected (a draw always refunds A and B in full, verified), so no funds are at risk and this is within the brief's wording (\"winner must be one of the two players unless draw\"). The risk is only off-chain: an indexer, leaderboard or payout reconciler that filters `Settled` by the `winner` topic without also reading `draw` will attribute a result to an address that never played. The contract comment at line 129 says the signed winner is retained in the event on purpose; INTEGRATION.md recommends the backend sign player A as the ignored winner. Suggested minimal fix if the author wants on-chain tightening without changing the schema: on a draw require `winner == address(0) || winner == playerA || winner == playerB`, or emit `address(0)` as the event winner for draws. This is a trust/asymmetry note, not a permission bypass; no revision is required by it.","line":138,"path":"src/GameEscrow.sol","reproduction":"State: alice open(ID, IMD, 1e18, \"pinball\"); bob join(ID). Result signer signs Result(ID, winner=carol (a non-player), draw=true, deadline=now+1h). Anyone calls settle(ID, carol, true, deadline, sig). Actual: call succeeds, emits Settled(ID, carol, true, 1e18, 0) with carol as the indexed winner topic; alice and bob each receive 1e18 back and carol receives nothing. Expected (by analogy with the decisive branch, which reverts InvalidWinner for carol): either revert, or an event whose winner topic is a player or address(0). Reproduced locally in a scratch Foundry test (test_DrawEmitsNonPlayerWinner) with vm.expectEmit on the exact event and balance assertions.","severity":"info","snippet":"        if (!draw && winner != gameMatch.playerA && winner != gameMatch.playerB) revert InvalidWinner();","title":"Draw branch skips the winner check, so Settled can carry an indexed non-player winner"}],"hash":"9067aa428bdf2ae393645400843687515a7592f5dbb8d78b7ff92b3a1fd57b91","nodeId":"91efbdec-9e6d-40f9-a39d-c4f0c544c35f","outcome":"completed","summary":"The JSON is valid, the quoted snippet matches line 138 exactly, and `git status` shows no tracked files changed. The only untracked artifacts are `.imd-findings.json` and `test/scratch/Probe.t.sol`, both of which are expected review outputs.\n\n**Review complete.** Findings are written to `.imd-findings.json`.\n\n**Result for the assigned area (Access Control, Trust Gap, Asymmetry): no permission bypass, trust-gap exploit or value-moving asymmetry found.** The permission model is small and consistent:\n\n- Owner is a compile-time constant matching the brief, so a factory deploy cannot capture it. The owner's only powers are signer rotation, limits within hard caps, and pausing open/join. None can move, redirect or trap a stake. A pause cannot trap funds because cancel, refund and settle stay live.\n- Every storage variable written by two paths uses the same validator on both. Limits are read only at open time, so setLimits has no retroactive or sandwichable effect. Signer rotation only invalidates pending signatures, and the 24-hour refund remains.\n- Deposits always pull from msg.sender, so the escrow cannot be used as a confused deputy against other approvers. Payouts go only to recorded players and the constant fee splitter.\n- Join, settle and refund time windows are exactly complementary with no overlap.\n\n**One info-level finding was recorded.** The draw branch of settle skips the winner-is-a-player check and still emits the signed winner as an indexed event topic, so a draw signed for a non-player yields a misleading Settled event. Funds are unaffected. It is documented as intentional and is within the brief's wording.\n\n**Verification done:** the existing suite passes with 70 tests. I ran six scratch probes confirming the traced claims, including the draw-event case and the front-run-join scenario, which a signed draw frees immediately. Mainnet checks confirmed the constant addresses: the owner is an EOA with an EIP-7702 delegation so it can sign results, IMD and SHIFT report 18 decimals, and the v1 escrow has owner and signer both equal to the same address.\n\n**Not reached:** the IMD token is an owned contract with about 23 KB of runtime, and I did not verify its source for transfer fees or blacklists. This is outside my area and recorded as an open trust assumption in the coverage record, not as a finding.","treeHash":null,"usage":{"cachedInputTokens":907712,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":21833,"runtime":"claude","turns":30,"wallClockMs":349820}},{"artifacts":[],"attempt":1,"bundleHash":"a56a6ee2e12c30ba92bd23365790031841b76ec6ccd8ff0bb1838e7a2ae40d03","device":"3311c48cd0280ed4","findings":[],"hash":"912dca51ae4075ef8183a3933452f017fe9cddd70656231a8972289623688df2","nodeId":"b3b4d724-fa8b-479f-8360-ca0113005f19","outcome":"completed","summary":"Implemented GameEscrow v2 with offline dependencies, comprehensive tests, and deployment/signing documentation.\n\nValidation passed:\n- `forge build`\n- `forge test`: 70 tests, including 8,192 stateful calls, using the required seed\n- `forge fmt --check`\n\nLive deployment remains with IdentityMD’s launch service; no transaction was broadcast.","treeHash":"bc6250d5d17a4c4528bdeabe128f2130c1f39185","usage":{"cachedInputTokens":1077120,"inputTokens":117677,"model":"gpt-6-astra","outputTokens":42027,"runtime":"codex","turns":6,"wallClockMs":741591}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7e929507773df661","findings":[{"citation":"resolved","description":"open() keys the record only by the caller-chosen matchId and makes every ID a permanent nonce (line 13), while cancel() returns the squatter's stake at once. Anyone watching the mempool can front-run player A's open(matchId,...) with their own open(matchId, IMD, 0.5e18, ...) followed by cancel(matchId) in the same block: the squatter loses nothing but gas, A's transaction reverts with MatchAlreadyExists, and the ID is unusable forever, so the server must mint a new ID and the attacker can repeat indefinitely (a liveness/DoS on lobby creation; with the documented zero-minimum option the squat is free of capital entirely). The same race lets a squatter open the ID with a different stake (up to the current max) before A; join(bytes32) takes whatever token/stake/opponent the record holds and gives B no way to pin the expected parameters, so a B that follows the server's 'join this ID' instruction without re-reading the record deposits the squatter's stake and is locked until the 24h refund. No funds are lost in either variant (the squatter cannot be paid unless the signer names them), which is why this is low. Reproduced in test/scratch/IdSquat.t.sol (2 passing tests). Possible fixes, each a scope decision because the brief fixes the open/join signatures: key the record by keccak256(playerA, matchId) so an ID can only collide with the same creator; or require the server's signature over (matchId, playerA, token, stake) in open; and/or add expected token/stake/playerA arguments to join that are compared against the stored record.","line":97,"path":"src/GameEscrow.sol","reproduction":"State: fresh GameEscrow, IMD limits 0.5e18..1000e18, alice and mallory each approved. (1) alice broadcasts open(ID, IMD, 1e18, 'pinball'). (2) mallory front-runs: open(ID, IMD, 0.5e18, 'pinball') then cancel(ID). Expected: alice's open succeeds (or mallory bears a cost). Actual: mallory's balance is unchanged, alice's open reverts MatchAlreadyExists, join(ID) reverts MatchClosed, and ID is dead forever. Variant: mallory front-runs with open(ID, IMD, 100e18) instead; alice's open reverts; bob's join(ID) succeeds and pulls 100e18 instead of the agreed 1e18 (bob balance 0), playerA == mallory.","severity":"low","snippet":"        if (matches[matchId].playerA != address(0)) revert MatchAlreadyExists();","title":"Any server-issued match ID can be burned or hijacked by a front-runner at gas-only cost (open + cancel); join cannot assert token, stake or opponent"},{"citation":"resolved","description":"README.md line 15 and lib/README.md line 10 instruct verifiers to run `sha256sum --check lib/SHA256SUMS`, and docs/SECURITY.md records that all 50 files matched. On the submitted tree the check reports 11 FAILED: openzeppelin IERC20Permit.sol, Address.sol, ECDSA.sol, MessageHashUtils.sol and forge-std StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, interfaces/IERC7540.sol, interfaces/IMulticall3.sol. I fetched the pinned upstream files (OpenZeppelin v5.0.2, forge-std v1.9.7): the upstream bytes hash to exactly the values in SHA256SUMS, so the sums are right and the local copies were changed, evidently by running `forge fmt` over lib/. A whitespace-insensitive comparison (and hex\"19_01\" vs hex\"1901\") shows the differences are formatting only, so ECDSA/SafeERC20 behaviour is unchanged and this is not a security defect in the escrow; but the recorded validation result is untrue, the stated provenance guarantee ('original source contents are unmodified') does not hold, and any reviewer or deployer following the README gets a failed integrity check. Fix: restore the 11 files to their upstream bytes (or regenerate SHA256SUMS from the vendored files and correct the SECURITY.md row), and keep lib/ out of future fmt runs.","line":16,"path":"docs/SECURITY.md","reproduction":"From the repository root run `sha256sum --check lib/SHA256SUMS`. Expected (per README.md:15, lib/README.md:10 and docs/SECURITY.md:16): every line OK. Actual: 11 lines FAILED, exit status 1, e.g. `lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol: FAILED`. `diff <(curl -s https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.0.2/contracts/utils/cryptography/ECDSA.sol) lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol` shows only a re-wrapped tryRecover signature.","severity":"low","snippet":"| Vendored file SHA-256 verification | All 50 source/license files matched |","title":"Vendored dependency integrity check fails: 11 lib files were reformatted after SHA256SUMS was written, so the documented `sha256sum --check` step and the SECURITY.md claim are false"},{"citation":"resolved","description":"Not a defect in this code; recorded because the owner is also the initial result signer and the only key that can rotate it. `cast code 0x6c4c78f2c3bdf4c4318fe305fb1c8bc1694d19b5` on Ethereum mainnet returns 23 bytes (0xef0100 || 20-byte address), i.e. the account is an EOA with an EIP-7702 delegation designator. ECDSA.recover still works for it (the escrow verifies raw secp256k1 signatures, not ERC-1271), so settle() is unaffected, but whichever implementation the delegation points to can act as the owner and signer. The operator should confirm the delegated implementation is theirs before enabling deposits and consider calling setSigner(serverEOA) at launch as README 'After launch' already suggests.","line":20,"path":"src/GameEscrow.sol","reproduction":"cast code 0x6c4c78f2c3bdf4c4318fe305fb1c8bc1694d19b5 --rpc-url <mainnet> -> 0xef0100... (23 bytes) rather than 0x. Observed 2026-10-18.","severity":"info","snippet":"    address public constant owner = 0x6C4c78f2C3bDf4C4318Fe305FB1C8BC1694d19b5;","title":"Trust note: the fixed owner/initial-signer address currently carries an EIP-7702 delegation on mainnet"}],"hash":"9f00932fa03605d40875641c8d9b90e27cd8eb2098d762c6c56fd37f34f69f43","nodeId":"02b7f39b-403c-47ec-88c7-44d7f6fd3734","outcome":"completed","summary":"Review complete. The findings file at `.imd-findings.json` holds 3 findings and 13 coverage rows, and no repository file was changed (git reports a clean tree; the scratch tests and findings file are ignored).\n\n**Outcome:** no loss-of-funds, wrong-payee or permanent-breakage defect in `GameEscrow`. Every entry point in my area traced as intended. The escrow is checks-effects-interactions throughout, the terminal flag is written before every transfer, signatures bind all four result fields plus chain and contract, and the join/settle windows are exact complements of the refund cutoffs, so no interleaving lets two outcomes both succeed.\n\n**Findings written (all low or info):**\n\n1. **Match-ID squatting at gas-only cost.** A front-runner can run `open` then `cancel` on any ID seen in the mempool, recovering the whole stake at once. The victim's `open` reverts and the ID is dead forever, so lobby creation can be denied indefinitely. A variant opens the ID with a larger stake, and a joiner who does not re-read the record deposits that amount. No funds are lost, so low. Reproduced in two passing scratch tests.\n2. **Vendored dependency check fails.** The README's `sha256sum --check lib/SHA256SUMS` step reports 11 files FAILED, while the security notes claim all 50 matched. I fetched the pinned upstream files: the checksum file is correct and the local copies were reformatted. I confirmed the differences are whitespace-only, so ECDSA and SafeERC20 behaviour is unchanged. Low, documentation and reproducibility only.\n3. **Info:** the fixed owner and initial signer address currently carries an EIP-7702 delegation on mainnet. Not a code defect, recorded as a trust note.\n\n**Extra verification done beyond the shipped suite:**\n- A fork rehearsal against the real IMD, SHIFT and fee-splitter code ran open, join, win and draw at odd and maximum stakes. Exact-delta deposits, payouts and splitter receipt all matched.\n- All 70 project tests pass, `forge fmt --check` passes.\n\n**Static-analysis leads dismissed:** the reentrancy-balance line on `_deposit` is covered by the guard and fixed plain tokens, the uninitialized `cap` is assigned on every non-reverting branch, and the timestamp comparisons implement the specified deadlines.\n\n**Not covered:** the fee splitter's internal distribution and the server's result pipeline are outside the tree and were not reviewed.","treeHash":null,"usage":{"cachedInputTokens":1535444,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":28907,"runtime":"claude","turns":34,"wallClockMs":475201}},{"artifacts":[],"attempt":1,"bundleHash":"bf7bfcd54d1efc4fdd167190cb7e48e9686adb576a1ddd2c5958ad1960e98279","device":"9523c8447c9485ed","findings":[],"hash":"a4652a25e78e245c2198ee286fb0d41df72074510daba0f649bca64738ad520f","nodeId":"6117592d-3e00-4f26-8ce2-2a88e72d18e1","outcome":"completed","summary":"Added 10 tests and strengthened invariants for custody, donations, signer rotation, allowances, rounding, and call ordering.\n\nOffline `forge build` and `forge test` passed with the supplied seed: **80 tests passed**, including **32,768 invariant calls**.\n\nChanges are confined to `test/`. No confirmed contract defects found.","treeHash":"ff4dc3b70cbda74cd3808ff6b366466228f426d7","usage":{"cachedInputTokens":1469696,"inputTokens":95255,"model":"gpt-6-astra","outputTokens":17390,"runtime":"codex","turns":6,"wallClockMs":403923}},{"artifacts":[],"attempt":1,"bundleHash":"ab85a564555dd73c8a4567cf94cd9b51cb71673d6a076662d2014e61804de980","device":"bc30da9213bc5d7a","findings":[],"hash":"ba2f21bec0e06a3627aff7585f86b1e1a76f3528867532064617bf4d1784fbc4","nodeId":"8925fe0a-4e96-4140-8623-1aa3d112de64","outcome":"completed","summary":"Created [launch.json](/home/imd11/.identitymd/work/e568de57-17f3-4cb0-b05f-37bc363e32df/8925fe0a-4e96-4140-8623-1aa3d112de64/launch.json) for `GameEscrow` with `[\"$owner\"]` as constructor arguments.\n\nManifest structure and compiled constructor ABI checks passed. `forge build` succeeded; all 70 tests passed using the supplied fuzz seed. Only `launch.json` changed.","treeHash":"bbd6f271b8913083e4dd14ac04c7d08b15d16423","usage":{"cachedInputTokens":202496,"inputTokens":23293,"model":"gpt-6-astra","outputTokens":1818,"runtime":"codex","turns":3,"wallClockMs":56056}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1731fbfe0c4574fb","findings":[{"citation":"resolved","description":"README.md line 15 instructs verifiers to run `sha256sum --check lib/SHA256SUMS`, docs/SECURITY.md line 16 records that all 50 files matched, and lib/README.md line 3 states the vendored sources are unmodified. None of this is true for the committed tree: 11 of the 50 files fail the check (OpenZeppelin Address.sol, ECDSA.sol, MessageHashUtils.sol, IERC20Permit.sol; forge-std StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, IERC7540.sol, IMulticall3.sol). I downloaded the pinned upstream files (OpenZeppelin v5.0.2, forge-std v1.9.7): the manifest hashes equal upstream, so the manifest is right and the tree's copies were altered after vendoring. Diffing shows only reformatting (a `forge fmt` pass: joined signature lines, `hex\"19_01\"` -> `hex\"1901\"`, `(bool success, )` -> `(bool success,)`), so compiled behaviour of ECDSA/MessageHashUtils is unchanged and this is not a logic defect. It is still a defect in the submitted artefact: the only provenance control the project ships fails, the validation record claims a pass that cannot have happened on this tree, and a later source-publication/attestation step that re-runs the documented check will fail. Fix: either restore the 11 files byte-for-byte from the upstream release archives (and exclude lib/ from `forge fmt`, e.g. `ignore = [\"lib/**\"]` in the [fmt] section), or regenerate lib/SHA256SUMS from the formatted files and correct the 'unmodified' wording; then re-run the check before recording its result.","line":16,"path":"docs/SECURITY.md","reproduction":"From the repository root run `sha256sum --check lib/SHA256SUMS`. Expected (per README.md line 15 and docs/SECURITY.md line 16): 50 lines 'OK'. Actual: 39 OK, 11 'FAILED', 'WARNING: 11 computed checksums did NOT match'. Example: `sha256sum lib/openzeppelin-contracts/contracts/utils/Address.sol` gives 2e8da894236dc598818e898ba20b31e294bf8592d156e780b845d36b61a35bad while lib/SHA256SUMS line 9 and upstream v5.0.2 both give b3710b1712637eb8c0df81912da3450da6ff67b0b3ed18146b033ed15b1aa3b9. `diff` of the tree's lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol against the upstream v5.0.2 file shows only `hex\"19_00\"`/`hex\"19_01\"` rewritten as `hex\"1900\"`/`hex\"1901\"` (lines 64 and 80).","severity":"low","snippet":"| Vendored file SHA-256 verification | All 50 source/license files matched |","title":"Documented vendored-dependency integrity check fails: 11 lib/ files differ from lib/SHA256SUMS and from the pinned upstream releases"},{"citation":"resolved","description":"open() keys matches only by the caller-chosen matchId and refuses any later open with the same id, and a record is never cleared (cancel/refund set settled=true, playerA stays). The server hands player A a session id to open with; a mempool observer who sees A's pending open(matchId, ...) can submit open(matchId, IMD, 0.5e18, anything) with higher gas, making A's transaction revert with MatchAlreadyExists, then call cancel(matchId) in the next block to get the 0.5 IMD back. Cost to the attacker is gas plus a one-block lock of the minimum stake; cost to the victim is a failed transaction and a new server-issued id each time. Repeated against every open this blocks lobby creation for the whole product on a public mempool. It also lets the squatter put an unexpected token/stake/opponent under an id the server already associates with A's session, which docs/INTEGRATION.md delegates to the backend to detect. Not a loss of escrowed funds. Minimal fix that keeps the requested interface: namespace the record by the opener, e.g. `bytes32 key = keccak256(abi.encode(msg.sender, matchId))` for storage/events (or key the mapping as matches[playerA][matchId]) so only A can consume A's id, or have the server co-sign the open. Either is a design decision for the author; reporting so it is made consciously.","line":97,"path":"src/GameEscrow.sol","reproduction":"Scratch test test_MatchIdSquatting (run, passes on this code): id = keccak256(\"server-assigned-session-id\"); mallory calls open(id, IMD, 0.5 ether, \"pinball\") first; alice's open(id, SHIFT, 20_000 ether, \"chess\") reverts MatchAlreadyExists; mallory calls cancel(id) and immediately receives her 0.5 IMD back; alice's open(id, ...) still reverts MatchAlreadyExists forever. Expected: A's server-issued id cannot be consumed by a third party. Actual: any address burns any id for the minimum stake's gas.","severity":"low","snippet":"        if (matches[matchId].playerA != address(0)) revert MatchAlreadyExists();","title":"Match IDs are a first-come global namespace: anyone can front-run open() with the same matchId at the minimum stake and burn it forever"},{"citation":"resolved","description":"fee = floor(2*stake*500/10000) = floor(stake/10), so the splitter is short by the fractional remainder (< 1 wei per match, which is the best an integer 5% can do) and receives exactly 0 when stake <= 9 wei. With the launch minima (0.5 IMD, 10,000 SHIFT) the fee is always >= 5e16 wei and the deviation is sub-wei; the zero-fee case needs setLimits(token, min < 10, max), which _setLimits allows (only min > max and max > cap are refused). Economic impact is negligible (at most 9 wei of fee per match) and the rounding direction is documented in README 'Payouts and events'. Recorded for completeness of the math review: if the owner ever wants sub-wei exactness or a non-zero fee guarantee, round the fee up (`(pot * FEE_BPS + BPS_DENOMINATOR - 1) / BPS_DENOMINATOR`) or enforce a hard floor on min in _setLimits; neither is required by the brief.","line":152,"path":"src/GameEscrow.sol","reproduction":"Scratch test test_FeeZeroBelowTenWei (run, passes on this code): owner calls setLimits(IMD, 1, 1000e18); alice open(id, IMD, 9, ...); bob join(id); settle(id, alice, false, deadline, sig). Expected 5% of the 18-wei pot: 0.9 wei. Actual: fee transferred to FEE_RECIPIENT = 0, winner receives 18. Odd-stake case: stake 0.5e18+1 gives pot 1e18+2, exact fee 5e16+0.1, actual fee 5e16, payout 9.5e17+2 (test_OddStakeRounding).","severity":"info","snippet":"            uint256 fee = pot * FEE_BPS / BPS_DENOMINATOR;","title":"Fee rounds down and truncates to zero for stakes below 10 wei (reachable only if the owner lowers a minimum below 10 wei)"}],"hash":"fafcc46fc11ff590f561203f1f7a35da0a8da465be685dfb6e3bb50a877f9f38","nodeId":"52bf18f6-a85d-419e-ab35-7c3e0dc0d80f","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. The working tree is otherwise untouched, with the scratch tests removed.\n\n**Outcome:** the math in GameEscrow holds. I found no fund-loss, misdirected-payment or overflow defect in the Math Precision, Boundary or Numerical Gap areas. Three low-value items are recorded, none with a proof since none is critical or high.\n\n**What I verified in my area**\n- Fee arithmetic: the fee is floor(stake/10), so the splitter deviates from exact 5% by under 1 wei, and the largest intermediate at the SHIFT hard cap is 1e30, far below overflow. Draws, refunds and cancels return stored amounts exactly, so the balance-equals-liabilities invariant holds.\n- Time cutoffs: join is refused and refund allowed at exactly open plus one hour, and settle is refused and refund allowed at exactly join plus 24 hours. There is no gap or overlap. A signature at its own deadline is accepted.\n- Deposits: the balance-delta check rejects short and excess credits, and the reentrancy guard blocks every re-entry path the test token can trigger.\n- Limits: ordered inclusive bounds within hard caps, max equal to cap allowed, stored stakes never repriced.\n\n**Findings written**\n1. **Low, docs/SECURITY.md line 16.** The documented vendored-file integrity check fails. Eleven of the fifty files in `lib/` differ from the manifest and from the pinned upstream releases, which I downloaded and diffed. Every difference is formatting only, so compiled behaviour is unchanged, but the security notes record a pass that cannot have happened on this tree.\n2. **Low, src/GameEscrow.sol line 97.** Match IDs form a global first-come namespace. Anyone can front-run a player's open with the same ID at the minimum stake, cancel immediately, and burn that ID forever. This is outside my area and is a griefing vector, not a loss of escrowed funds.\n3. **Info, src/GameEscrow.sol line 152.** The fee truncates to zero for stakes under 10 wei. This needs the owner to lower a minimum below 10 wei first, and the loss is at most 9 wei per match.\n\n**Coverage:** all nine entry points have rows, plus two invariants and the dependency-integrity check. Everything I checked was reached. Static-analysis leads on reentrancy and the uninitialised local were traced and rejected.\n\n**Not verified:** the live IMD and SHIFT token implementations on mainnet, since no fork was available. The review assumes they remain plain 18-decimal ERC-20s, as the brief states.","treeHash":null,"usage":{"cachedInputTokens":1412446,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":28922,"runtime":"claude","turns":34,"wallClockMs":431679}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dc7103e773ea194a","findings":[{"citation":"resolved","description":"Merged from audit_math, audit_flow and audit_economics (same root cause). open() keys a match only by the caller-supplied matchId, refuses any later open with that id, and no outcome ever clears the record (cancel/refund/settle set settled=true and playerA stays). cancel() returns player A's stake immediately with no delay or fee. A mempool observer who sees Alice's pending open(id, ...) can submit, with higher priority, a contract call doing open(id, IMD, 0.5e18, x) then cancel(id) in one transaction: the squatter's stake comes straight back, Alice's transaction reverts with MatchAlreadyExists, and the id is unusable forever, so the server must issue a new id and the attack can repeat. Variant: the squatter opens the id with a different stake (up to the current max); join(bytes32) has no expected token/stake/opponent parameter, so a player B who follows the server's 'join this id' instruction without re-reading matches(id) deposits the squatter's stake and is locked until settle or the 24-hour refund. No escrowed funds can be taken (payout goes only to recorded players or the splitter), so this is a liveness/denial-of-service and UX defect, not a loss of funds. The behaviour follows the brief's 'matchId must be new' literally, so any fix is a scope decision for the requester: key the record by keccak256(abi.encode(msg.sender, matchId)) so only Alice can consume Alice's id (keeps the open/join signatures), or require a server signature over (matchId, playerA, token, stake) in open, and/or give join expected token/stake/playerA arguments compared against the record. docs/INTEGRATION.md already delegates id unpredictability and participant checks to the backend; the contract does not enforce them.","line":97,"path":"src/GameEscrow.sol","reproduction":"State: fresh GameEscrow with launch limits, IMD mock at the fixed address; alice and a Squatter contract hold >= 0.5 IMD and approved the escrow. 1) Squatter.squat(escrow, id, IMD, 0.5e18) executes open(id, IMD, 0.5e18, 'x') then cancel(id) in one call. Actual: squatter IMD balance unchanged (only gas spent); matches(id).playerA == squatter, matches(id).settled == true. 2) alice: open(id, IMD, 100e18, 'pinball') reverts MatchAlreadyExists. 3) bob: join(id) reverts MatchClosed. Expected: a server-issued id cannot be consumed by a third party, or doing so costs more than gas. Reproduced in test/scratch/Review.t.sol test_MatchIdSquatting (passes on this code, demonstrating the behaviour). The existing test test_MatchIdAndLabelMayBeZeroButIdNeverReused (test/GameEscrowFlows.t.sol lines 228-239) already shows re-open after cancel reverts forever.","severity":"low","snippet":"        if (matches[matchId].playerA != address(0)) revert MatchAlreadyExists();","title":"Caller-chosen match IDs are a permanent global namespace: a front-runner burns any server-issued matchId at gas-only cost (open + cancel), and can seat an unexpected stake under it"},{"citation":"resolved","description":"Merged from audit_math and audit_flow. README.md line 15 and lib/README.md instruct verifiers to run `sha256sum --check lib/SHA256SUMS`, and docs/SECURITY.md line 16 records that all 50 files matched. On the committed tree 11 files fail: OpenZeppelin Address.sol, ECDSA.sol, MessageHashUtils.sol, IERC20Permit.sol and forge-std StdAssertions.sol, StdJson.sol, StdToml.sol, Vm.sol, console.sol, interfaces/IERC7540.sol, interfaces/IMulticall3.sol. I fetched the pinned upstream OpenZeppelin v5.0.2 files: their hashes equal the values in lib/SHA256SUMS, so the manifest is right and the tree's copies were altered after vendoring. `diff -w` against upstream shows only `forge fmt` style changes (re-wrapped function signatures, hex\"19_01\" -> hex\"1901\"), so the compiled ECDSA/EIP-712/SafeERC20 behaviour is unchanged and this is not a logic or security defect in the escrow. It is a defect in the submitted artefact: the only provenance control the project ships fails, the validation table claims a pass that cannot have happened on this tree, and lib/README's 'unmodified' statement is false. Fix: restore the 11 files byte-for-byte from the upstream releases and exclude lib/ from fmt (e.g. `ignore = [\"lib/**\"]` under [fmt]), or regenerate lib/SHA256SUMS from the formatted files and correct the wording; then re-run the check before recording its result.","line":16,"path":"docs/SECURITY.md","reproduction":"From the repository root run `sha256sum --check lib/SHA256SUMS`. Expected per README.md line 15 and docs/SECURITY.md line 16: 50 lines 'OK', exit 0. Actual: 39 OK, 11 FAILED, 'WARNING: 11 computed checksums did NOT match', exit 1 (e.g. `lib/forge-std/src/Vm.sol: FAILED`, `lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol: FAILED`). Upstream check: `curl https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.0.2/contracts/utils/cryptography/ECDSA.sol | sha256sum` gives 37828cb50b47bcc51c7b770bde15d5885d871ef1e67028057a0b788c3568726e, identical to the lib/SHA256SUMS entry; `diff -w` of that file against the tree's copy shows only the tryRecover(bytes32,uint8,bytes32,bytes32) signature re-wrapped; MessageHashUtils.sol differs only in hex\"19_00\"/hex\"19_01\" written as hex\"1900\"/hex\"1901\" (lines 64, 80).","severity":"low","snippet":"| Vendored file SHA-256 verification | All 50 source/license files matched |","title":"Documented vendored-dependency integrity check fails: 11 of 50 lib/ files were reformatted after lib/SHA256SUMS was written, so the recorded validation result is untrue"},{"citation":"resolved","description":"From audit_permissions. The decisive branch requires winner to be player A or B (line 138), but the draw branch validates nothing about winner and still emits it as the indexed `winner` topic of Settled. Transfers are unaffected: a draw always refunds A and B their exact stake and nothing goes to the signed winner, so no funds are at risk, and the brief's wording ('winner must be one of the two players unless draw') permits it. The only impact is off-chain: an indexer or leaderboard that filters Settled by the winner topic without also reading `draw` attributes a result to an address that never played. If the author wants to tighten it without changing the signing schema: on a draw require winner == address(0) || winner == playerA || winner == playerB, or emit address(0) as the event winner for draws. No revision is required by this note.","line":146,"path":"src/GameEscrow.sol","reproduction":"State: alice open(id, IMD, 1e18, 'pinball'); bob join(id); signer signs Result(id, winner=carol (non-player), draw=true, deadline=now+1h). Anyone calls settle(id, carol, true, deadline, sig). Actual: succeeds and emits Settled(id, carol, true, 1e18, 0) with carol as the indexed winner; alice and bob each receive 1e18; carol receives nothing. The same signer result with draw=false for a second match reverts InvalidWinner. Reproduced in test/scratch/Review.t.sol test_DrawEmitsNonPlayerWinner with vm.expectEmit on the exact event and balance assertions.","severity":"info","snippet":"            emit Settled(matchId, winner, true, stake, 0);","title":"Draw branch skips the winner check, so Settled can carry a non-player address as its indexed winner topic"},{"citation":"resolved","description":"From audit_economics. The struct follows the brief exactly, so this is a documented trust assumption rather than a code defect. join() is permissionless and there is no reserved opponent, so the on-chain playerB can differ from the opponent the server matched off-chain, and the contract only checks that the signed winner is one of the two recorded players. If the server signs 'matchId M, winner Alice' from its own lobby record (Alice vs Bob) while Carol front-ran Bob's join, Carol loses her stake to Alice without having played her. A result pre-signed before join (winner = playerA) makes whoever joins lose instantly. docs/INTEGRATION.md requires the backend to read matches(M) and reject unexpected participants before signing and never to pre-sign; those server-side checks are load-bearing for fund safety and the contract cannot enforce them. Binding playerA, playerB, token and stake into the signed struct would close the seam on-chain but changes the agreed signing schema, so it is a scope decision for the requester.","line":138,"path":"src/GameEscrow.sol","reproduction":"State: alice open(M, IMD, 100e18, 'pinball') intending to play bob. carol calls join(M) first; bob's join(M) reverts AlreadyJoined. Server signs Result(M, alice, false, deadline) without re-reading matches(M).playerB. Anyone calls settle(M, alice, false, deadline, sig). Actual: line 138 passes (alice is playerA), signature verifies, alice receives 190e18, splitter 10e18, carol's 100e18 is gone. Expected under the product's intent: the result applies only to the match the server adjudicated between its recorded players. Reproduced in test/scratch/Review.t.sol test_UnboundOpponentLoses.","severity":"info","snippet":"        if (!draw && winner != gameMatch.playerA && winner != gameMatch.playerB) revert InvalidWinner();","title":"Trust seam: the signed Result binds only matchId/winner/draw/deadline, not the players, token or stake, so a result signed for the server's lobby pays out against whoever actually joined on-chain"},{"citation":"resolved","description":"From audit_math. fee = floor(2*stake*500/10000) = floor(stake/10): the splitter is short by the fractional remainder (< 1 wei per match, the best an integer 5% can do) and receives exactly 0 when stake <= 9 wei, in which case settle also performs a zero-value safeTransfer to the splitter. With the launch minima (0.5 IMD, 10,000 SHIFT) the fee is always >= 5e16 wei and the deviation is sub-wei; the zero-fee case needs setLimits(token, min < 10, max), which _setLimits allows (only min > max and max > cap are refused; README documents a zero minimum as allowed). Economic impact is negligible and the rounding direction is documented in README 'Payouts and events'. Recorded for completeness: if a non-zero fee or sub-wei exactness is ever wanted, round the fee up or enforce a floor on min in _setLimits; neither is required by the brief.","line":152,"path":"src/GameEscrow.sol","reproduction":"State: owner calls setLimits(IMD, 1, 1000e18); alice open(id, IMD, 9, 'pinball'); bob join(id); settle(id, alice, false, deadline, sig). Expected 5% of the 18-wei pot: 0.9 wei. Actual: fee transferred to FEE_RECIPIENT = 0, alice receives 18. Odd stake at launch minimum: stake 0.5e18+1 gives pot 1e18+2, exact fee 5e16+0.1, actual fee 5e16, payout 9.5e17+2, escrow balance 0 afterwards. Reproduced in test/scratch/Review.t.sol test_FeeZeroBelowTenWei and test_OddStakeRounding.","severity":"info","snippet":"            uint256 fee = pot * FEE_BPS / BPS_DENOMINATOR;","title":"Fee rounds down and truncates to zero for stakes below 10 wei, reachable only if the owner lowers a minimum below 10 wei (allowed by _setLimits)"},{"citation":"resolved","description":"From audit_flow; not a defect in this code, recorded because the owner is also the initial result signer and the only key that can rotate it or pause deposits. On mainnet the account's code is 23 bytes beginning 0xef0100, i.e. an EOA with an EIP-7702 delegation designator pointing at 0x000000005c84f8fd50b21cac312528a64437030e. ECDSA.recover still works for it (the escrow verifies raw secp256k1 signatures, not ERC-1271), so settle() is unaffected, but whatever the delegated implementation allows can act as owner and signer. The operator should confirm the delegated implementation is theirs before enabling deposits, and consider calling setSigner(serverEOA) at launch as README 'After launch' already suggests.","line":20,"path":"src/GameEscrow.sol","reproduction":"`cast code 0x6c4c78f2c3bdf4c4318fe305fb1c8bc1694d19b5 --rpc-url https://ethereum-rpc.publicnode.com` returns 0xef0100000000005c84f8fd50b21cac312528a64437030e (23 bytes) rather than 0x. Observed 2026-10-18. Expected for a plain EOA owner/signer: empty code.","severity":"info","snippet":"    address public constant owner = 0x6C4c78f2C3bDf4C4318Fe305FB1C8BC1694d19b5;","title":"Trust note: the fixed owner / initial result signer address currently carries an EIP-7702 delegation on Ethereum mainnet"}],"hash":"fdae59b6de4663929dd1e1de6e49b1eff0686a00ec771cf36fd5c89235035b29","nodeId":"bdddee89-7d3d-4b78-bce8-0bdfe4c7d7d8","outcome":"completed","summary":"Review complete. The findings file is written and validated, and every snippet matches the tree.\n\n**Outcome:** no critical, high or medium defects. GameEscrow holds on every entry point I traced, and the full suite of 80 tests passes, including the 256-run stateful invariant. All six specialist claims reproduced against the code, so none were dropped. Three reports of the same match-ID squatting root cause merged into one finding, and two reports of the checksum failure merged into one.\n\n**Findings kept in `.imd-findings.json`:**\n\n- **Low, match ID squatting.** Any observer can front-run a server-issued matchId with open then cancel in one transaction at gas-only cost, burning the id forever or seating an unexpected stake under it. No escrowed funds can be taken. Reproduced in a scratch test. The fix is a scope decision since the brief fixes the open/join signatures.\n- **Low, vendored dependency integrity.** The documented checksum step fails on 11 of 50 lib files. I fetched the pinned upstream OpenZeppelin v5.0.2 files and confirmed the mismatch is formatting-only, so compiled behaviour is unchanged, but the SECURITY.md validation row is untrue.\n- **Info, four notes.** Draw settlements emit a non-player winner topic. The signed Result does not bind players, token or stake, so server-side participant checks are load-bearing. The fee truncates to zero below 10 wei if the owner lowers a minimum that far. The fixed owner address carries an EIP-7702 delegation on mainnet, which I confirmed with `cast code` against a public RPC.\n\n**My own pass** found nothing further. I checked the Slither leads (reentrancy-balance, uninitialized cap, timestamps), fee arithmetic bounds, boundary consistency between join/settle cutoffs and refund eligibility, signature replay and domain binding, pause scope, constructor safety for CREATE2 factory deployment, and the launch manifest. All hold.\n\n**Coverage:** all 9 entry points answered, plus 4 invariant rows. Only `open` is marked as a finding, at low severity. The scratch test lives in `test/scratch/Review.t.sol` and is not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":756288,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":21827,"runtime":"claude","turns":23,"wallClockMs":409113}}],"verification":[{"checks":[{"durationMs":2491,"exitCode":0,"name":"build","output":"Compiling 46 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.41s\nCompiler run successful!\n","passed":true},{"durationMs":3880,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 16 tests for test/GameEscrowAdversarial.t.sol:GameEscrowAdversarialTest\n[PASS] test_ExactDepositRejectsBothUnderAndOverCreditOnOpenAndJoin() (gas: 1487430)\n[PASS] test_FailedCancelTransferRollsBackAndCanRetry() (gas: 476246)\n[PASS] test_FailedFeeTransferRollsBackWinnerPaymentAndSignatureConsumption() (gas: 761382)\n[PASS] test_FailedSecondDrawTransferRollsBackFirstRefund() (gas: 719404)\n[PASS] test_FailedSecondTimeoutRefundRollsBackFirstRefund() (gas: 683280)\n[PASS] test_FalseReturningPayoutRollsBackSettlement() (gas: 703379)\n[PASS] test_FalseReturningTokenCannotJoinAndStateRollsBack() (gas: 583633)\n[PASS] test_FalseReturningTokenCannotOpenAndRollbackFreesId() (gas: 511883)\n[PASS] test_NoReturnTokenSupportedOnDepositSettlementAndRefund() (gas: 966616)\n[PASS] test_OneFailedMatchDoesNotBlockUnrelatedMatches() (gas: 975975)\n[PASS] test_ReentrancyAcrossMatchesIsBlockedDuringDeposit() (gas: 725674)\n[PASS] test_ReentrancyOnCancelCannotCancelTwice() (gas: 489805)\n[PASS] test_ReentrancyOnJoinCannotSettleBeforeDepositCompletes() (gas: 734165)\n[PASS] test_ReentrancyOnOpenCannotJoinBeforeDepositCompletes() (gas: 442699)\n[PASS] test_ReentrancyOnRefundCannotRefundTwice() (gas: 634798)\n[PASS] test_ReentrancyOnSettlementCannotSettleTwice() (gas: 839752)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 57.59ms (6.69ms CPU time)\n\nRan 16 tests for test/GameEscrowSignatures.t.sol:GameEscrowSignaturesTest\n[PASS] testFuzz_AnyNonPlayerWinnerRejected(address) (runs: 1024, μ: 135083, ~: 135139)\n[PASS] test_ChangingChainIdInvalidatesOldDomainAndAcceptsNewDomain() (gas: 208553)\n[PASS] test_DomainAndDigestMatchIndependentEIP712Encoding() (gas: 41305)\n[PASS] test_EveryResultFieldIsSigned() (gas: 578324)\n[PASS] test_ExpiredSignatureRejectedAndExactDeadlineAccepted() (gas: 288701)\n[PASS] test_HighSMalleableSignatureRejected() (gas: 130921)\n[PASS] test_InvalidVRejected() (gas: 57923)\n[PASS] test_MalformedAndZeroRecoverySignaturesRejected() (gas: 215449)\n[PASS] test_PersonalSignSignatureRejected() (gas: 58166)\n[PASS] test_ReplayAndConflictingResultBothRejectedAfterSettlement() (gas: 242714)\n[PASS] test_SignerRotationInvalidatesPendingSignature() (gas: 217040)\n[PASS] test_WinnerMustBeAPlayerUnlessDraw() (gas: 185995)\n[PASS] test_WrongChainDomainRejected() (gas: 133754)\n[PASS] test_WrongContractDomainRejected() (gas: 136561)\n[PASS] test_WrongNameOrVersionRejected() (gas: 123985)\n[PASS] test_WrongSignerRejected() (gas: 133732)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 60.89ms (66.79ms CPU time)\n\nRan 16 tests for test/GameEscrowAdmin.t.sol:GameEscrowAdminTest\n[PASS] testFuzz_ConfiguredInclusiveBoundsUsedForFutureOpens(bool,uint256,uint256) (runs: 1024, μ: 456489, ~: 456771)\nLogs:\n  Bound result 29\n  Bound result 36246100265927963829\n\n[PASS] testFuzz_OnlyOwnerCanAdminister(address) (runs: 1024, μ: 135102, ~: 135156)\n[PASS] testFuzz_SetLimitsAcceptsExactlyOrderedBoundsBelowCap(bool,uint256,uint256) (runs: 1024, μ: 43089, ~: 39127)\n[PASS] testFuzz_StakesOutsideInitialLimitsRefused(bool,uint256,bool) (runs: 1024, μ: 85488, ~: 85607)\nLogs:\n  Bound result 115792089237316195423570985008687907853269984665640564038457584007913129643149\n\n[PASS] test_DirectDonationDoesNotChangePotOrAllowSweep() (gas: 556103)\n[PASS] test_FactoryAndCreate2DeploymentOnEmptyChainKeepRequestedOwner() (gas: 534635)\n[PASS] test_InitialConfigurationAndConstructorEvents() (gas: 115402)\n[PASS] test_LimitChangesNeverAlterAnExistingMatchOrBlockJoin() (gas: 1665416)\n[PASS] test_OwnerCannotWithdrawRedirectUpgradeOrTransferOwnership() (gas: 504321)\n[PASS] test_PauseAllowsSettlementDrawBothRefundsCancelAndAdminChanges() (gas: 1978745)\n[PASS] test_PauseBlocksOpenAndJoinAndEmitsEvents() (gas: 754783)\n[PASS] test_RuntimeHasNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 4163628)\n[PASS] test_SetLimitsAtHardCapsAndRefuseAboveOrReversed() (gas: 338825)\n[PASS] test_UnsupportedTokenRejectedForOpenAndLimits() (gas: 104160)\n[PASS] test_ZeroMinimumIsAnExplicitOwnerChoiceWithinSpecifiedBounds() (gas: 478936)\n[PASS] test_ZeroSignerRejectedAtConstructionAndRotation() (gas: 3841)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 63.58ms (258.09ms CPU time)\n\nRan 21 tests for test/GameEscrowFlows.t.sol:GameEscrowFlowsTest\n[PASS] testFuzz_AnyStakeConservesPotAndRoundsFeeDown(bool,uint256,bool) (runs: 1024, μ: 560983, ~: 562428)\nLogs:\n  Bound result 836052205830180649015899971\n\n[PASS] testFuzz_DrawAlwaysRefundsBothExactly(bool,uint256) (runs: 1024, μ: 507672, ~: 507717)\nLogs:\n  Bound result 26789171517223025875133822\n\n[PASS] test_BothTokensAtMinimumMiddleAndMaximum() (gas: 4032782)\n[PASS] test_CancelAfterOpenTimeoutStillWorks() (gas: 298277)\n[PASS] test_CancelByOtherPlayerOrOwnerRefusedAndJoinedCannotCancel() (gas: 441863)\n[PASS] test_CancelImmediatelyBothTokensAndOnlyOnce() (gas: 763000)\n[PASS] test_ConcurrentMatchesKeepTokensAndPotsSeparate() (gas: 1222434)\n[PASS] test_DrawBothTokensRefundsFullStakeAndNoFee() (gas: 1043122)\n[PASS] test_DrawMayUseZeroWinner() (gas: 470736)\n[PASS] test_InsufficientAllowanceOrBalanceCannotOpenOrJoin() (gas: 847360)\n[PASS] test_JoinAlwaysTakesStoredAmountAndToken() (gas: 403945)\n[PASS] test_JoinAtDeadlineRefusedAndRefundWorks() (gas: 335298)\n[PASS] test_JoinOwnMatchAndThirdPlayerRefused() (gas: 417350)\n[PASS] test_JoinedRefund24HoursAfterJoinBothTokens() (gas: 1260069)\n[PASS] test_MatchIdAndLabelMayBeZeroButIdNeverReused() (gas: 341661)\n[PASS] test_SettleAtTimeoutRefusedAndRefundWorks() (gas: 498290)\n[PASS] test_SettleJustBeforeTimeoutWorks() (gas: 476900)\n[PASS] test_TerminalMatchesRejectEverySubsequentAction() (gas: 2097581)\n[PASS] test_UnjoinedCannotSettle() (gas: 251838)\n[PASS] test_UnjoinedRefundAtExactHourBothTokensByAnyone() (gas: 921459)\n[PASS] test_UnknownMatchRefusedByAllLifecycleMethods() (gas: 133977)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 63.72ms (136.85ms CPU time)\n\nRan 1 test for test/GameEscrowInvariant.t.sol:GameEscrowInvariantTest\n[PASS] invariant_EveryBalanceAndMatchAgreesWithIndependentModel() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+-------------+-------+---------+----------╮\n| Contract      | Selector    | Calls | Reverts | Discards |\n+==========================================================+\n| EscrowHandler | advanceTime | 1052  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | cancel      | 1032  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | join        | 1030  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | open        | 1023  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | refund      | 1002  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | setLimits   | 978   | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | setPaused   | 1028  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | settle      | 1047  | 0       | 0        |\n╰---------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000001\n  Bound result 2000000000000000001\n  Bound result 20000000000000000000001\n  Bound result 7\n  Bound result 2000000000000000001\n  Bound result 7191\n  Bound result 6379\n  Bound result 10412\n  Bound result 500000000000000001\n  Bound result 46201\n  Bound result 70099\n  Bound result 10000000000000000000001\n  Bound result 1055239862\n  Bound result 900000000000000000\n  Bound result 13000000000000000000\n  Bound result 9\n  Bound result 6328\n  Bound result 7839256357602486430518\n  Bound result 37532988984818234606\n  Bound result 9610\n  Bound result 9079008999133734465263\n  Bound result 8076\n  Bound result 423402636933770900250652179\n  Bound result 3813151\n  Bound result 9079008999133734463252\n  Bound result 41074\n  Bound result 4447\n  Bound result 3\n  Bound result 14583\n  Bound result 9\n  Bound result 15213\n  Bound result 5548002985707156202044\n  Bound result 3000000000000000009\n  Bound result 1692\n  Bound result 2000000000000000001\n  Bound result 14115\n  Bound result 242\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.79s (3.79s CPU time)\n\nRan 5 test suites in 3.80s (4.04s CPU time): 70 tests passed, 0 failed, 0 skipped (70 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"GameEscrow.cancel(bytes32)\",\"GameEscrow.join(bytes32)\",\"GameEscrow.open(bytes32,address,uint256,bytes32)\",\"GameEscrow.pause()\",\"GameEscrow.refund(bytes32)\",\"GameEscrow.setLimits(address,uint256,uint256)\",\"GameEscrow.setSigner(address)\",\"GameEscrow.settle(bytes32,address,bool,uint256,bytes)\",\"GameEscrow.unpause()\"],\"files\":{\".gitignore\":6,\"LICENSE\":21,\"README.md\":102,\"docs/INTEGRATION.md\":56,\"docs/SECURITY.md\":55,\"foundry.toml\":21,\"remappings.txt\":2,\"src/GameEscrow.sol\":238,\"test/GameEscrowAdmin.t.sol\":302,\"test/GameEscrowAdversarial.t.sol\":231,\"test/GameEscrowFlows.t.sol\":351,\"test/GameEscrowInvariant.t.sol\":303,\"test/GameEscrowSignatures.t.sol\":226,\"test/helpers/EscrowTestBase.sol\":136,\"test/helpers/TestToken.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":904,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/GameEscrow.sol:231: Reentrancy in GameEscrow._deposit(address,address,uint256) (src/GameEscrow.sol#231-237):\n[medium/medium] uninitialized-local at src/GameEscrow.sol:222: GameEscrow._setLimits(address,uint256,uint256).cap (src/GameEscrow.sol#222) is a local variable never initialized\n[low/medium] timestamp at src/GameEscrow.sol:116: GameEscrow.join(bytes32) (src/GameEscrow.sol#116-126) uses timestamp for comparisons\n[low/medium] timestamp at src/GameEscrow.sol:161: GameEscrow.refund(bytes32) (src/GameEscrow.sol#161-172) uses timestamp for comparisons\n[low/medium] timestamp at src/GameEscrow.sol:231: GameEscrow._deposit(address,address,uint256) (src/GameEscrow.sol#231-237) uses timestamp for comparisons\n[low/medium] timestamp at src/GameEscrow.sol:130: GameEscrow.settle(bytes32,address,bool,uint256,bytes) (src/GameEscrow.sol#130-158) uses timestamp for comparisons","passed":true},{"durationMs":337,"exitCode":0,"name":"aderyn","output":"[low] centralization-risk at src/GameEscrow.sol:185: Centralization Risk (4 places)\n[low] large-numeric-literal at src/GameEscrow.sol:22: Large Numeric Literal (5 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"912dca51ae4075ef8183a3933452f017fe9cddd70656231a8972289623688df2","verifiedTreeHash":"bc6250d5d17a4c4528bdeabe128f2130c1f39185","verifierVersion":"0.1.0+a6ee6548"},{"checks":[{"durationMs":2977,"exitCode":0,"name":"build","output":"Compiling 47 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.89s\nCompiler run successful!\n","passed":true},{"durationMs":13936,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 16 tests for test/GameEscrowSignatures.t.sol:GameEscrowSignaturesTest\n[PASS] testFuzz_AnyNonPlayerWinnerRejected(address) (runs: 1024, μ: 135083, ~: 135139)\n[PASS] test_ChangingChainIdInvalidatesOldDomainAndAcceptsNewDomain() (gas: 208553)\n[PASS] test_DomainAndDigestMatchIndependentEIP712Encoding() (gas: 41305)\n[PASS] test_EveryResultFieldIsSigned() (gas: 578324)\n[PASS] test_ExpiredSignatureRejectedAndExactDeadlineAccepted() (gas: 288701)\n[PASS] test_HighSMalleableSignatureRejected() (gas: 130921)\n[PASS] test_InvalidVRejected() (gas: 57923)\n[PASS] test_MalformedAndZeroRecoverySignaturesRejected() (gas: 215449)\n[PASS] test_PersonalSignSignatureRejected() (gas: 58166)\n[PASS] test_ReplayAndConflictingResultBothRejectedAfterSettlement() (gas: 242714)\n[PASS] test_SignerRotationInvalidatesPendingSignature() (gas: 217040)\n[PASS] test_WinnerMustBeAPlayerUnlessDraw() (gas: 185995)\n[PASS] test_WrongChainDomainRejected() (gas: 133754)\n[PASS] test_WrongContractDomainRejected() (gas: 136561)\n[PASS] test_WrongNameOrVersionRejected() (gas: 123985)\n[PASS] test_WrongSignerRejected() (gas: 133732)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 136.79ms (107.91ms CPU time)\n\nRan 16 tests for test/GameEscrowAdversarial.t.sol:GameEscrowAdversarialTest\n[PASS] test_ExactDepositRejectsBothUnderAndOverCreditOnOpenAndJoin() (gas: 1487430)\n[PASS] test_FailedCancelTransferRollsBackAndCanRetry() (gas: 476246)\n[PASS] test_FailedFeeTransferRollsBackWinnerPaymentAndSignatureConsumption() (gas: 761382)\n[PASS] test_FailedSecondDrawTransferRollsBackFirstRefund() (gas: 719404)\n[PASS] test_FailedSecondTimeoutRefundRollsBackFirstRefund() (gas: 683280)\n[PASS] test_FalseReturningPayoutRollsBackSettlement() (gas: 703379)\n[PASS] test_FalseReturningTokenCannotJoinAndStateRollsBack() (gas: 583633)\n[PASS] test_FalseReturningTokenCannotOpenAndRollbackFreesId() (gas: 511883)\n[PASS] test_NoReturnTokenSupportedOnDepositSettlementAndRefund() (gas: 966616)\n[PASS] test_OneFailedMatchDoesNotBlockUnrelatedMatches() (gas: 975975)\n[PASS] test_ReentrancyAcrossMatchesIsBlockedDuringDeposit() (gas: 725674)\n[PASS] test_ReentrancyOnCancelCannotCancelTwice() (gas: 489805)\n[PASS] test_ReentrancyOnJoinCannotSettleBeforeDepositCompletes() (gas: 734165)\n[PASS] test_ReentrancyOnOpenCannotJoinBeforeDepositCompletes() (gas: 442699)\n[PASS] test_ReentrancyOnRefundCannotRefundTwice() (gas: 634798)\n[PASS] test_ReentrancyOnSettlementCannotSettleTwice() (gas: 839752)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 142.31ms (6.97ms CPU time)\n\nRan 21 tests for test/GameEscrowFlows.t.sol:GameEscrowFlowsTest\n[PASS] testFuzz_AnyStakeConservesPotAndRoundsFeeDown(bool,uint256,bool) (runs: 1024, μ: 561216, ~: 562427)\nLogs:\n  Bound result 9357233011851834059705\n\n[PASS] testFuzz_DrawAlwaysRefundsBothExactly(bool,uint256) (runs: 1024, μ: 507675, ~: 507717)\nLogs:\n  Bound result 500000000000000003\n\n[PASS] test_BothTokensAtMinimumMiddleAndMaximum() (gas: 4032782)\n[PASS] test_CancelAfterOpenTimeoutStillWorks() (gas: 298277)\n[PASS] test_CancelByOtherPlayerOrOwnerRefusedAndJoinedCannotCancel() (gas: 441863)\n[PASS] test_CancelImmediatelyBothTokensAndOnlyOnce() (gas: 763000)\n[PASS] test_ConcurrentMatchesKeepTokensAndPotsSeparate() (gas: 1222434)\n[PASS] test_DrawBothTokensRefundsFullStakeAndNoFee() (gas: 1043122)\n[PASS] test_DrawMayUseZeroWinner() (gas: 470736)\n[PASS] test_InsufficientAllowanceOrBalanceCannotOpenOrJoin() (gas: 847360)\n[PASS] test_JoinAlwaysTakesStoredAmountAndToken() (gas: 403945)\n[PASS] test_JoinAtDeadlineRefusedAndRefundWorks() (gas: 335298)\n[PASS] test_JoinOwnMatchAndThirdPlayerRefused() (gas: 417350)\n[PASS] test_JoinedRefund24HoursAfterJoinBothTokens() (gas: 1260069)\n[PASS] test_MatchIdAndLabelMayBeZeroButIdNeverReused() (gas: 341661)\n[PASS] test_SettleAtTimeoutRefusedAndRefundWorks() (gas: 498290)\n[PASS] test_SettleJustBeforeTimeoutWorks() (gas: 476900)\n[PASS] test_TerminalMatchesRejectEverySubsequentAction() (gas: 2097581)\n[PASS] test_UnjoinedCannotSettle() (gas: 251838)\n[PASS] test_UnjoinedRefundAtExactHourBothTokensByAnyone() (gas: 921459)\n[PASS] test_UnknownMatchRefusedByAllLifecycleMethods() (gas: 133977)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 144.48ms (299.12ms CPU time)\n\nRan 9 tests for test/GameEscrowProperties.t.sol:GameEscrowPropertiesTest\n[PASS] testFuzz_ArbitraryMatchIdAndGameSurviveFullLifecycle(bytes32,bytes32,bool) (runs: 1000, μ: 535517, ~: 536087)\n[PASS] testFuzz_CancelAndJoinOrderingNeverPaysBothPaths(bool,uint256,bool) (runs: 1000, μ: 505087, ~: 436027)\nLogs:\n  Bound result 999500000000000000100\n\n[PASS] testFuzz_DomainUsesChainIdAcrossRunnerRange(uint64,bool) (runs: 1000, μ: 587148, ~: 587148)\n[PASS] testFuzz_DonationCannotSubsidizeAnUnderfundedJoin(bool,uint256,uint256) (runs: 1000, μ: 888528, ~: 888592)\nLogs:\n  Bound result 34628674197346870317911574\n  Bound result 346286741973468703179115740\n\n[PASS] testFuzz_LimitChangesPreserveTermsThroughEveryExit(bool,uint256,uint256,uint256,uint8,bytes32) (runs: 1000, μ: 600341, ~: 649761)\nLogs:\n  Bound result 999500000000025797657\n  Bound result 2881224104992926468340\n  Bound result 3101061553888481018456\n\n[PASS] test_ApprovalForOtherTokenCannotFundJoin() (gas: 1559897)\n[PASS] test_FeeStepsAtSingleWeiBoundariesForBothTokens() (gas: 11098303)\n[PASS] test_HardCapAndOneWeiBelowItSettleWithoutDustForBothTokens() (gas: 2118180)\n[PASS] test_InitialOutOfRangeEdgesLeaveBalancesAllowancesAndIdUntouched() (gas: 1301670)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 144.59ms (723.87ms CPU time)\n\nRan 16 tests for test/GameEscrowAdmin.t.sol:GameEscrowAdminTest\n[PASS] testFuzz_ConfiguredInclusiveBoundsUsedForFutureOpens(bool,uint256,uint256) (runs: 1024, μ: 456547, ~: 456768)\nLogs:\n  Bound result 21251679683751224898490659\n  Bound result 978748320316248775101511394\n\n[PASS] testFuzz_OnlyOwnerCanAdminister(address) (runs: 1024, μ: 135102, ~: 135156)\n[PASS] testFuzz_SetLimitsAcceptsExactlyOrderedBoundsBelowCap(bool,uint256,uint256) (runs: 1024, μ: 42980, ~: 39126)\n[PASS] testFuzz_StakesOutsideInitialLimitsRefused(bool,uint256,bool) (runs: 1024, μ: 85486, ~: 85601)\nLogs:\n  Bound result 90000\n\n[PASS] test_DirectDonationDoesNotChangePotOrAllowSweep() (gas: 556103)\n[PASS] test_FactoryAndCreate2DeploymentOnEmptyChainKeepRequestedOwner() (gas: 534635)\n[PASS] test_InitialConfigurationAndConstructorEvents() (gas: 115402)\n[PASS] test_LimitChangesNeverAlterAnExistingMatchOrBlockJoin() (gas: 1665416)\n[PASS] test_OwnerCannotWithdrawRedirectUpgradeOrTransferOwnership() (gas: 504321)\n[PASS] test_PauseAllowsSettlementDrawBothRefundsCancelAndAdminChanges() (gas: 1978745)\n[PASS] test_PauseBlocksOpenAndJoinAndEmitsEvents() (gas: 754783)\n[PASS] test_RuntimeHasNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 4163628)\n[PASS] test_SetLimitsAtHardCapsAndRefuseAboveOrReversed() (gas: 338825)\n[PASS] test_UnsupportedTokenRejectedForOpenAndLimits() (gas: 104160)\n[PASS] test_ZeroMinimumIsAnExplicitOwnerChoiceWithinSpecifiedBounds() (gas: 478936)\n[PASS] test_ZeroSignerRejectedAtConstructionAndRotation() (gas: 3841)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 144.61ms (421.07ms CPU time)\n\nRan 2 tests for test/GameEscrowInvariant.t.sol:GameEscrowInvariantTest\n[PASS] invariant_EveryBalanceAndMatchAgreesWithIndependentModel() (runs: 256, calls: 32768, reverts: 0)\n\n╭---------------+-------------------+-------+---------+----------╮\n| Contract      | Selector          | Calls | Reverts | Discards |\n+================================================================+\n| EscrowHandler | advanceTime       | 2382  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | cancel            | 2358  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | changeAllowance   | 2338  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | donate            | 2300  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | join              | 2310  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | open              | 2383  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | progress          | 2374  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | refund            | 2335  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | replayArchived    | 2346  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | rotateSigner      | 2320  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | setLimits         | 2231  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | setPaused         | 2365  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | settle            | 2341  | 0       | 0        |\n|---------------+-------------------+-------+---------+----------|\n| EscrowHandler | unauthorizedAdmin | 2385  | 0       | 0        |\n╰---------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000001\n  Bound result 2000000000000000001\n  Bound result 20000000000000000000001\n  Bound result 426\n  Bound result 101\n  Bound result 36633757983008880158431499\n  Bound result 85730316516210554\n  Bound result 104316822317877662334\n  Bound result 49990000000000000001000001\n  Bound result 59213\n  Bound result 999500000000000000111\n  Bound result 3045\n  Bound result 14946\n  Bound result 190\n  Bound result 2000000000000000000\n  Bound result 46\n  Bound result 64\n  Bound result 11940\n  Bound result 11580690\n  Bound result 10177\n  Bound result 190\n  Bound result 11580690\n  Bound result 49990000000000000000020233\n  Bound result 64\n  Bound result 127\n  Bound result 7495\n  Bound result 885189935066456\n  Bound result 40\n  Bound result 86400\n  Bound result 11\n  Bound result 21377\n  Bound result 244\n  Bound result 4511869433294154514373\n  Bound result 55830647609508\n  Bound result 7889096849301322353805\n  Bound result 9740741174049790897291\n  Bound result 10000000000000000000000\n  Bound result 3672\n  Bound result 49990000000000000000004126\n  Bound result 20871\n  Bound result 15275\n  Bound result 9258221623742342724191\n  Bound result 9258221623742342717512\n  Bound result 11\n  Bound result 25\n\n[PASS] test_HandlerExercisesEverySuccessfulTransitionAndArchivedReplay() (gas: 4462115)\nLogs:\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000001\n  Bound result 2000000000000000001\n  Bound result 20000000000000000000001\n  Bound result 7\n  Bound result 1000000000000000000\n  Bound result 3600\n  Bound result 10000000000000000000000\n  Bound result 86400\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 13.83s (13.83s CPU time)\n\nRan 6 test suites in 13.83s (14.54s CPU time): 80 tests passed, 0 failed, 0 skipped (80 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"GameEscrow.cancel(bytes32)\",\"GameEscrow.join(bytes32)\",\"GameEscrow.open(bytes32,address,uint256,bytes32)\",\"GameEscrow.pause()\",\"GameEscrow.refund(bytes32)\",\"GameEscrow.setLimits(address,uint256,uint256)\",\"GameEscrow.setSigner(address)\",\"GameEscrow.settle(bytes32,address,bool,uint256,bytes)\",\"GameEscrow.unpause()\"],\"files\":{\".gitignore\":6,\"LICENSE\":21,\"README.md\":102,\"docs/INTEGRATION.md\":56,\"docs/SECURITY.md\":55,\"foundry.toml\":21,\"remappings.txt\":2,\"src/GameEscrow.sol\":238,\"test/GameEscrowAdmin.t.sol\":302,\"test/GameEscrowAdversarial.t.sol\":231,\"test/GameEscrowFlows.t.sol\":351,\"test/GameEscrowInvariant.t.sol\":473,\"test/GameEscrowProperties.t.sol\":263,\"test/GameEscrowSignatures.t.sol\":226,\"test/helpers/EscrowTestBase.sol\":136,\"test/helpers/TestToken.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a4652a25e78e245c2198ee286fb0d41df72074510daba0f649bca64738ad520f","verifiedTreeHash":"ff4dc3b70cbda74cd3808ff6b366466228f426d7","verifierVersion":"0.1.0+a6ee6548"},{"checks":[{"durationMs":2389,"exitCode":0,"name":"build","output":"Compiling 46 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.30s\nCompiler run successful!\n","passed":true},{"durationMs":3869,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 16 tests for test/GameEscrowAdversarial.t.sol:GameEscrowAdversarialTest\n[PASS] test_ExactDepositRejectsBothUnderAndOverCreditOnOpenAndJoin() (gas: 1487430)\n[PASS] test_FailedCancelTransferRollsBackAndCanRetry() (gas: 476246)\n[PASS] test_FailedFeeTransferRollsBackWinnerPaymentAndSignatureConsumption() (gas: 761382)\n[PASS] test_FailedSecondDrawTransferRollsBackFirstRefund() (gas: 719404)\n[PASS] test_FailedSecondTimeoutRefundRollsBackFirstRefund() (gas: 683280)\n[PASS] test_FalseReturningPayoutRollsBackSettlement() (gas: 703379)\n[PASS] test_FalseReturningTokenCannotJoinAndStateRollsBack() (gas: 583633)\n[PASS] test_FalseReturningTokenCannotOpenAndRollbackFreesId() (gas: 511883)\n[PASS] test_NoReturnTokenSupportedOnDepositSettlementAndRefund() (gas: 966616)\n[PASS] test_OneFailedMatchDoesNotBlockUnrelatedMatches() (gas: 975975)\n[PASS] test_ReentrancyAcrossMatchesIsBlockedDuringDeposit() (gas: 725674)\n[PASS] test_ReentrancyOnCancelCannotCancelTwice() (gas: 489805)\n[PASS] test_ReentrancyOnJoinCannotSettleBeforeDepositCompletes() (gas: 734165)\n[PASS] test_ReentrancyOnOpenCannotJoinBeforeDepositCompletes() (gas: 442699)\n[PASS] test_ReentrancyOnRefundCannotRefundTwice() (gas: 634798)\n[PASS] test_ReentrancyOnSettlementCannotSettleTwice() (gas: 839752)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 60.16ms (10.90ms CPU time)\n\nRan 16 tests for test/GameEscrowSignatures.t.sol:GameEscrowSignaturesTest\n[PASS] testFuzz_AnyNonPlayerWinnerRejected(address) (runs: 1024, μ: 135083, ~: 135139)\n[PASS] test_ChangingChainIdInvalidatesOldDomainAndAcceptsNewDomain() (gas: 208553)\n[PASS] test_DomainAndDigestMatchIndependentEIP712Encoding() (gas: 41305)\n[PASS] test_EveryResultFieldIsSigned() (gas: 578324)\n[PASS] test_ExpiredSignatureRejectedAndExactDeadlineAccepted() (gas: 288701)\n[PASS] test_HighSMalleableSignatureRejected() (gas: 130921)\n[PASS] test_InvalidVRejected() (gas: 57923)\n[PASS] test_MalformedAndZeroRecoverySignaturesRejected() (gas: 215449)\n[PASS] test_PersonalSignSignatureRejected() (gas: 58166)\n[PASS] test_ReplayAndConflictingResultBothRejectedAfterSettlement() (gas: 242714)\n[PASS] test_SignerRotationInvalidatesPendingSignature() (gas: 217040)\n[PASS] test_WinnerMustBeAPlayerUnlessDraw() (gas: 185995)\n[PASS] test_WrongChainDomainRejected() (gas: 133754)\n[PASS] test_WrongContractDomainRejected() (gas: 136561)\n[PASS] test_WrongNameOrVersionRejected() (gas: 123985)\n[PASS] test_WrongSignerRejected() (gas: 133732)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 61.29ms (66.65ms CPU time)\n\nRan 16 tests for test/GameEscrowAdmin.t.sol:GameEscrowAdminTest\n[PASS] testFuzz_ConfiguredInclusiveBoundsUsedForFutureOpens(bool,uint256,uint256) (runs: 1024, μ: 456489, ~: 456771)\nLogs:\n  Bound result 110272301932205462307\n  Bound result 9889728660857705818120\n\n[PASS] testFuzz_OnlyOwnerCanAdminister(address) (runs: 1024, μ: 135102, ~: 135156)\n[PASS] testFuzz_SetLimitsAcceptsExactlyOrderedBoundsBelowCap(bool,uint256,uint256) (runs: 1024, μ: 43089, ~: 39127)\n[PASS] testFuzz_StakesOutsideInitialLimitsRefused(bool,uint256,bool) (runs: 1024, μ: 85488, ~: 85607)\nLogs:\n  Bound result 115792089237316195423570985008687907853269984665640514039457584074456189830565\n\n[PASS] test_DirectDonationDoesNotChangePotOrAllowSweep() (gas: 556103)\n[PASS] test_FactoryAndCreate2DeploymentOnEmptyChainKeepRequestedOwner() (gas: 534635)\n[PASS] test_InitialConfigurationAndConstructorEvents() (gas: 115402)\n[PASS] test_LimitChangesNeverAlterAnExistingMatchOrBlockJoin() (gas: 1665416)\n[PASS] test_OwnerCannotWithdrawRedirectUpgradeOrTransferOwnership() (gas: 504321)\n[PASS] test_PauseAllowsSettlementDrawBothRefundsCancelAndAdminChanges() (gas: 1978745)\n[PASS] test_PauseBlocksOpenAndJoinAndEmitsEvents() (gas: 754783)\n[PASS] test_RuntimeHasNoForbiddenOpcodesAndFitsDeploymentLimit() (gas: 4163628)\n[PASS] test_SetLimitsAtHardCapsAndRefuseAboveOrReversed() (gas: 338825)\n[PASS] test_UnsupportedTokenRejectedForOpenAndLimits() (gas: 104160)\n[PASS] test_ZeroMinimumIsAnExplicitOwnerChoiceWithinSpecifiedBounds() (gas: 478936)\n[PASS] test_ZeroSignerRejectedAtConstructionAndRotation() (gas: 3841)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 61.38ms (254.82ms CPU time)\n\nRan 21 tests for test/GameEscrowFlows.t.sol:GameEscrowFlowsTest\n[PASS] testFuzz_AnyStakeConservesPotAndRoundsFeeDown(bool,uint256,bool) (runs: 1024, μ: 560983, ~: 562428)\nLogs:\n  Bound result 2202\n\n[PASS] testFuzz_DrawAlwaysRefundsBothExactly(bool,uint256) (runs: 1024, μ: 507672, ~: 507717)\nLogs:\n  Bound result 709352850170709034024\n\n[PASS] test_BothTokensAtMinimumMiddleAndMaximum() (gas: 4032782)\n[PASS] test_CancelAfterOpenTimeoutStillWorks() (gas: 298277)\n[PASS] test_CancelByOtherPlayerOrOwnerRefusedAndJoinedCannotCancel() (gas: 441863)\n[PASS] test_CancelImmediatelyBothTokensAndOnlyOnce() (gas: 763000)\n[PASS] test_ConcurrentMatchesKeepTokensAndPotsSeparate() (gas: 1222434)\n[PASS] test_DrawBothTokensRefundsFullStakeAndNoFee() (gas: 1043122)\n[PASS] test_DrawMayUseZeroWinner() (gas: 470736)\n[PASS] test_InsufficientAllowanceOrBalanceCannotOpenOrJoin() (gas: 847360)\n[PASS] test_JoinAlwaysTakesStoredAmountAndToken() (gas: 403945)\n[PASS] test_JoinAtDeadlineRefusedAndRefundWorks() (gas: 335298)\n[PASS] test_JoinOwnMatchAndThirdPlayerRefused() (gas: 417350)\n[PASS] test_JoinedRefund24HoursAfterJoinBothTokens() (gas: 1260069)\n[PASS] test_MatchIdAndLabelMayBeZeroButIdNeverReused() (gas: 341661)\n[PASS] test_SettleAtTimeoutRefusedAndRefundWorks() (gas: 498290)\n[PASS] test_SettleJustBeforeTimeoutWorks() (gas: 476900)\n[PASS] test_TerminalMatchesRejectEverySubsequentAction() (gas: 2097581)\n[PASS] test_UnjoinedCannotSettle() (gas: 251838)\n[PASS] test_UnjoinedRefundAtExactHourBothTokensByAnyone() (gas: 921459)\n[PASS] test_UnknownMatchRefusedByAllLifecycleMethods() (gas: 133977)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 61.37ms (131.09ms CPU time)\n\nRan 1 test for test/GameEscrowInvariant.t.sol:GameEscrowInvariantTest\n[PASS] invariant_EveryBalanceAndMatchAgreesWithIndependentModel() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+-------------+-------+---------+----------╮\n| Contract      | Selector    | Calls | Reverts | Discards |\n+==========================================================+\n| EscrowHandler | advanceTime | 1052  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | cancel      | 1032  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | join        | 1030  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | open        | 1023  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | refund      | 1002  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | setLimits   | 978   | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | setPaused   | 1028  | 0       | 0        |\n|---------------+-------------+-------+---------+----------|\n| EscrowHandler | settle      | 1047  | 0       | 0        |\n╰---------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000001\n  Bound result 2000000000000000001\n  Bound result 20000000000000000000001\n  Bound result 7\n  Bound result 2000000000000000001\n  Bound result 7191\n  Bound result 6379\n  Bound result 10412\n  Bound result 500000000000000001\n  Bound result 46201\n  Bound result 70099\n  Bound result 10000000000000000000001\n  Bound result 1055239862\n  Bound result 900000000000000000\n  Bound result 13000000000000000000\n  Bound result 9\n  Bound result 6328\n  Bound result 7839256357602486430518\n  Bound result 37532988984818234606\n  Bound result 9610\n  Bound result 9079008999133734465263\n  Bound result 8076\n  Bound result 423402636933770900250652179\n  Bound result 3813151\n  Bound result 9079008999133734463252\n  Bound result 41074\n  Bound result 4447\n  Bound result 3\n  Bound result 14583\n  Bound result 9\n  Bound result 15213\n  Bound result 5548002985707156202044\n  Bound result 3000000000000000009\n  Bound result 1692\n  Bound result 2000000000000000001\n  Bound result 14115\n  Bound result 242\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.79s (3.79s CPU time)\n\nRan 5 test suites in 3.79s (4.03s CPU time): 70 tests passed, 0 failed, 0 skipped (70 total tests)\n","passed":true},{"durationMs":39,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"GameEscrow.cancel(bytes32)\",\"GameEscrow.join(bytes32)\",\"GameEscrow.open(bytes32,address,uint256,bytes32)\",\"GameEscrow.pause()\",\"GameEscrow.refund(bytes32)\",\"GameEscrow.setLimits(address,uint256,uint256)\",\"GameEscrow.setSigner(address)\",\"GameEscrow.settle(bytes32,address,bool,uint256,bytes)\",\"GameEscrow.unpause()\"],\"files\":{\".gitignore\":6,\"LICENSE\":21,\"README.md\":102,\"docs/INTEGRATION.md\":56,\"docs/SECURITY.md\":55,\"foundry.toml\":21,\"launch.json\":10,\"remappings.txt\":2,\"src/GameEscrow.sol\":238,\"test/GameEscrowAdmin.t.sol\":302,\"test/GameEscrowAdversarial.t.sol\":231,\"test/GameEscrowFlows.t.sol\":351,\"test/GameEscrowInvariant.t.sol\":303,\"test/GameEscrowSignatures.t.sol\":226,\"test/helpers/EscrowTestBase.sol\":136,\"test/helpers/TestToken.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ba2f21bec0e06a3627aff7585f86b1e1a76f3528867532064617bf4d1784fbc4","verifiedTreeHash":"bbd6f271b8913083e4dd14ac04c7d08b15d16423","verifierVersion":"0.1.0+a6ee6548"}]}