{"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":"ecfb5994-9bb6-4f81-ad34-04e9f17496e7","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"40663ed05cb690d9696b8d19baeef7994584441da6870393919d2ba293d5ad9e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"004e7022ac93bbb28f3f987c25ac7e50aabd52ca2506d848f221d6cf5d306a7b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"762767ed43a4a0ac94dda039a88a2efc6914557a18bf31fcadda5ef38ef607b5","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"3e139c771e9419b34cd0d184143727098830ba1540ffdcbb9dd56c1a9b7ed3c7","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"baa1440d47b889f899817a5d7259e5be781852e0e27104cb1c20036f3dbe9364","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"05aa8d98b4a021236670f2dc57baa8efbaaf06bac4f1e293ce885eae132a7ba4","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"c6d622a0cccdd0328552835201830600e596cc1eb226fa5f3bec32a1f31c3e61","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"9d30abf8c6dd645407a251d69ccd1c7a6a6badd09a5e59427cd9797c53d83dd8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Build and deploy TokenDeals on Ethereum: an escrow desk where anyone sells any ERC-20 token to anyone at a fixed price, all or part of it. The seller deposits the tokens into the contract and opens a deal; each buy settles in one transaction: tokens to the buyer, the full price to the seller, and a fee the buyer pays on top of the price to the NIGHTSHIFT FeeSplitter 0x1119d5df2f046bc2d3c0f6279ef2e9ce49422d44 (fixed forever, accepts ETH and any ERC-20). Nobody, the owner included, can ever move escrowed tokens or payments.\n\nPayment coins: ETH (address(0)) and an allowlist set at deploy: USDC 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 (6 decimals, cap 2500), USDT 0xdac17f958d2ee523a2206206994597c13d831ec7 (6 decimals, cap 2500), IMD 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 (18 decimals, cap 500), SHIFT 0xe5e58e53d0bb204aa62c995a6c8cc8a48e60c3ac (18 decimals, cap 40000000). Every payment coin is a plain ERC-20 without transfer fees; use SafeERC20 (USDT returns no bool). Per-buy cap on the total paid, in whole coins: ETH 1 and the caps above; setMaxPay(coin, max) changes a cap (0 = no cap); setPayCoin(coin, allowed) adds or removes a coin; removing only stops new deals in it.\n\nPrice: each deal has price = payment-coin base units per 1e18 base units of the sold token. cost(amount) = Math.mulDiv(amount, price, 1e18, Rounding.Ceil). fee = cost * bps / 10000 rounded down, where bps is feeBps (100 at deploy) or holderFeeBps (50) for holders. MAX_FEE_BPS = 200 is a constant; setFees(feeBps, holderFeeBps) requires holderFeeBps <= feeBps <= MAX_FEE_BPS.\n\nHolder pass: EIP-712, domain name \"TokenDeals\", version \"1\", chainId, this contract; struct HolderPass(address recipient, uint64 expiry), signed by passSigner (set by the owner with setPassSigner). An empty pass means the normal fee. A non-empty pass must be signed by passSigner for this recipient, not expired, and expire at most 24 hours after block.timestamp, or the buy reverts.\n\nFunctions:\n1. open(address token, uint256 amount, address payCoin, uint256 price, uint256 minBuy, uint64 expiry, address buyer) returns (uint256 dealId): token has code and is not payCoin; payCoin is allowed; amount > 0, price > 0; expiry after block.timestamp and at most 365 days ahead; buyer = address(0) for a public deal, or the only address that may receive this deal. transferFrom the seller; the deal holds what actually arrived (balance after minus before), so tokens with a transfer tax work; reverts if nothing arrived. minBuy <= that amount. Deal ids count up from 1. Emits DealOpened.\n2. buy(uint256 dealId, uint256 amount, address recipient, uint256 maxTotal, uint64 passExpiry, bytes pass) payable: deal open and not expired; recipient != address(0) and equal to the deal's buyer if one is set; 0 < amount <= left and amount >= minBuy unless amount == left (the rest may always be bought); total = cost + fee <= maxTotal and within the coin's cap. msg.sender may differ from recipient, so cross-chain services (Relay, LI.FI) can buy for a user. ETH deals: msg.value must equal total. Coin deals: msg.value must be 0 and total is pulled from msg.sender. Then left -= amount (closed at 0), amount of the token goes to recipient, cost to the seller, fee to the FeeSplitter. If an ETH transfer to the seller or the FeeSplitter fails, the amount is credited to owed[that address] and the buy still succeeds. Emits Bought (holder = a valid pass was used).\n3. update(uint256 dealId, uint256 price, uint256 minBuy, uint64 expiry): seller only, open deal, same rules as open; buyers are protected by maxTotal. Emits DealUpdated.\n4. cancel(uint256 dealId): seller only, any time, also when expired or paused: sends left back to the seller and closes the deal. Emits DealCancelled(dealId, returned).\n5. claimOwed(): sends the caller's owed ETH. Emits OwedClaimed.\n6. Views: deals(dealId) returning (address seller, address token, address payCoin, uint256 held, uint256 left, uint256 price, uint256 minBuy, uint64 expiry, address buyer, bool open), held being what arrived at open; dealCount(); quote(dealId, amount, bool holder) returning cost, fee, total; payCoin(address); maxPay(address); feeBps(); holderFeeBps(); passSigner(); owed(address); FEE_TO().\n\nEvents, exactly these signatures: DealOpened(uint256 indexed dealId, address indexed seller, address indexed token, address payCoin, uint256 held, uint256 price, uint256 minBuy, uint64 expiry, address buyer); Bought(uint256 indexed dealId, address indexed payer, address indexed recipient, uint256 amount, uint256 cost, uint256 fee, bool holder); DealUpdated(uint256 indexed dealId, uint256 price, uint256 minBuy, uint64 expiry); DealCancelled(uint256 indexed dealId, uint256 returned); OwedClaimed(address indexed account, uint256 amount); FeesSet(uint256 feeBps, uint256 holderFeeBps); PassSignerSet(address signer); PayCoinSet(address indexed coin, bool allowed); MaxPaySet(address indexed coin, uint256 max).\n\nOwner: 0x6c4c78f2c3bdf4c4318fe305fb1c8bc1694d19b5. The owner can only call setFees within the cap, setPassSigner, setPayCoin, setMaxPay, and pause/unpause, which stops open, update and buy only (cancel and claimOwed always work). The owner can never move, take or redirect escrowed tokens or payments, change FEE_TO, or close someone's deal. No upgradeability, no delegatecall, no selfdestruct, no token sweeping; tokens sent outside open stay in the contract. Rebasing tokens are not supported. Reentrancy-safe (checks-effects-interactions plus a guard on every state-changing function), SafeERC20, Solidity 0.8.x, NatSpec on every external function, events for every state change.\n\nTests (Foundry): full flows paying in ETH, a 6-decimal coin, a no-bool-return coin like USDT and an 18-decimal coin; partial buys, buying the rest below minBuy, private deals, buys for another recipient; exact cost and fee maths with rounding at odd prices and amounts, 1% and 0.5% with a pass; maxTotal, caps, wrong msg.value, ETH sent to a coin deal; expired, cancelled, closed and unknown deals; a sold token with a 2% transfer tax; update and cancel by non-sellers refused; cancel while paused; a seller contract that rejects ETH (owed, then claimOwed); reentrancy from a malicious token, a malicious seller and a malicious recipient; passes with a wrong signer, recipient, chain or contract, expired or too far ahead; fee changes above the cap refused; owner unable to move funds; fuzz and invariant tests that for every token the contract holds at least the sum of left over its open deals, the seller gets exactly cost and the FeeSplitter exactly fee on every buy.","parentJobId":null,"planHash":"b625a7c9bac397e8c9c66892ea317d2959890358216837ec34204f8be71b8e03","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"ecfb5994-9bb6-4f81-ad34-04e9f17496e7","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2655-build-deploy-tokendeals-ethereum"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52323","feedbackHash":"d913db5f004825af6df73b5401bc3d5bbfb726bc7694376980b7b65895267918","nodeKey":"audit_economics","submissionHash":"40663ed05cb690d9696b8d19baeef7994584441da6870393919d2ba293d5ad9e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52599","feedbackHash":"b5623a823d437144524077a9a580567f9f1072f86d59d7db0a8c16145af00f30","nodeKey":"audit_flow","submissionHash":"004e7022ac93bbb28f3f987c25ac7e50aabd52ca2506d848f221d6cf5d306a7b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52478","feedbackHash":"182e5c9c84038d23d0f17055874e4f262c40e0da9b0a61db81c7f83b0cd94e3f","nodeKey":"audit_judge","submissionHash":"762767ed43a4a0ac94dda039a88a2efc6914557a18bf31fcadda5ef38ef607b5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52289","feedbackHash":"c923bde59d452b1dea79be77c721fd91253ad442eaf3fe132867aa66bfa2edfb","nodeKey":"audit_math","submissionHash":"3e139c771e9419b34cd0d184143727098830ba1540ffdcbb9dd56c1a9b7ed3c7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51083","feedbackHash":"c0f6740ddf612a53dc2df00bfb387d0aa9f19e1ed6b93dda019998066bd0e13b","nodeKey":"audit_permissions","submissionHash":"baa1440d47b889f899817a5d7259e5be781852e0e27104cb1c20036f3dbe9364","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50982","feedbackHash":"5e683298d9f6120980e79b77cae57f72969a4a306865bf13dd53f0a1480914fa","nodeKey":"build_contract_project","submissionHash":"05aa8d98b4a021236670f2dc57baa8efbaaf06bac4f1e293ce885eae132a7ba4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"53944","feedbackHash":"b991f26c00d04b3fff5eaea2b57acc9d7b7b853baa02a7752262b32ca371381d","nodeKey":"manifest","submissionHash":"c6d622a0cccdd0328552835201830600e596cc1eb226fa5f3bec32a1f31c3e61","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51483","feedbackHash":"1949a0c3d1fb8bde8ec3176b855ed9f2393d0457383d77d1c83169b81f330591","nodeKey":"write_foundry_tests","submissionHash":"9d30abf8c6dd645407a251d69ccd1c7a6a6badd09a5e59427cd9797c53d83dd8","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9cf4771bfcbbff23079d539d1022742323d54ab5113f06c40f88b9f805790e4b","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a315ee4cb81c640c","findings":[{"citation":"resolved","description":"Periphery / execution-trace boundary. _payETH forwards at most ETH_PUSH_GAS = 50_000 gas (plus the 2,300 value stipend, so ~52,300 available to the callee). A seller that is a contract whose receive() legitimately does more work than that (for example a splitter-style wallet that makes a cold external call and writes two fresh storage slots, ~66k gas) is not paid; its cost is silently moved to owed[seller] and the buy succeeds. claimOwed() pays only msg.sender, so the credit can only ever be recovered if the seller contract itself can issue an arbitrary call to TokenDeals. Many such wallets cannot, and the brief forbids any other party, the owner included, from moving payments, so the ETH is stuck in the desk forever while the buyer keeps the tokens. This is the behaviour the brief asks for on a failed push, so it is reported as a documented limitation with a concrete trigger rather than a spec violation: the gas bound is a builder choice, and the honest fix is either documentation (sellers must be EOAs or contracts able to call claimOwed) or a higher bound. I verified the fixed FeeSplitter is not affected: its live code at 0x1119d5Df2f046bC2d3c0F6279EF2E9cE49422d44 has an empty receive() (trace shows 55 gas, no proxy slot, not upgradeable), so with the stipend its push can never fail, which matters because the FeeSplitter's ABI has no function able to call claimOwed().","line":354,"path":"src/TokenDeals.sol","reproduction":"State: a seller contract S whose receive() writes two fresh storage slots and increments a counter (3 cold SSTOREs, ~66k gas) and exposes no arbitrary-call function. S approves 10e18 TOKEN and calls open(TOKEN, 10e18, address(0), 0.01 ether, 0, now+30d, address(0)) -> dealId 1. Payer calls buy{value: 0.101 ether}(1, 10e18, payer, 0.101 ether, 0, \"\"). Expected (brief: 'the full price to the seller'): S.balance == 0.1 ether. Actual: S.balance == 0, owed[S] == 0.1 ether, desk.balance == 0.1 ether, buy succeeds, payer holds 10e18 TOKEN. Nobody can claim: claimOwed() from any other account reverts NothingOwed. Control: the same receiver with one fresh slot fewer (~44k gas) is paid in full, so the boundary is the 50k constant. Scratch test test/scratch/PushGasBoundary.t.sol (both cases pass as written, i.e. they document the behaviour).","severity":"low","snippet":"        (bool success,) = payable(account).call{value: amount, gas: ETH_PUSH_GAS}(\"\");\n        if (!success) {\n            owed[account] += amount;","title":"ETH proceeds of a seller contract whose receive() needs more than ~52.3k gas are credited to owed[] and are unrecoverable if that contract cannot call claimOwed()"}],"hash":"004e7022ac93bbb28f3f987c25ac7e50aabd52ca2506d848f221d6cf5d306a7b","nodeId":"0b5887f1-9ad7-4f79-b0a9-934ba32c0908","outcome":"completed","summary":"Review complete. The findings file is valid, the quoted snippet matches lines 354 to 356 of the contract, and the scratch test lives only under test/scratch.\n\n**Outcome.** The control-flow area of TokenDeals holds up. I traced every entry point through its checks, state writes and external calls against the Execution Trace, Periphery and First Principles guides and found no critical, high or medium defect. The existing suite of 82 tests passes and covers every item the brief's test list names.\n\n**One low finding, written to .imd-findings.json with a reproduction.** The ETH push to a seller forwards at most 50k gas. A seller that is a contract whose receive needs more than roughly 52,300 gas is silently credited to owed instead of paid, and if that contract cannot issue an arbitrary call it can never run claimOwed, so the ETH stays in the desk forever. The brief mandates the credit path, so this is a documented limitation with a concrete trigger rather than a spec breach. A scratch test shows a receiver with two fresh storage writes is paid and one with three is credited.\n\n**Periphery checks done against the live chain.** The FeeSplitter at the fixed address has an empty receive, no proxy slot and no upgrade path, and its ABI has no function able to call claimOwed. The stipend alone covers its receive, so a fee push to it cannot fail. That matters because a failed fee push would otherwise be unrecoverable.\n\n**Leads I examined and rejected.** The static analysis lines on reentrancy, strict equality and the ignored tryRecover return all sit behind the guard or are handled explicitly. Fee-on-transfer sold tokens, pay coins escrowed in other deals, recipient hooks, malicious tokens and sellers, pass replay across chain or contract, expired-deal renewal, pause interplay and owner powers all behave as the brief specifies.\n\n**Coverage record.** All eleven entry points have a row, ten hold and buy carries the finding. Five extra rows cover the escrow, exact-payout and no-custody invariants, pass validation, and the test suite itself. Nothing in my area was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1310602,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":40674,"runtime":"claude","turns":33,"wallClockMs":675136}},{"artifacts":[],"attempt":1,"bundleHash":"efae62deaf053f39803214df9123d440ad9c8c92db1c80f0993d939c55ab70b2","device":"a7313281793d3077","findings":[],"hash":"05aa8d98b4a021236670f2dc57baa8efbaaf06bac4f1e293ce885eae132a7ba4","nodeId":"7c5896d1-217b-48e5-9b47-6ebeff28c2c2","outcome":"completed","summary":"Implemented TokenDeals with vendored dependencies, adversarial tests, invariants, and [deployment documentation](/root/nft1/.identitymd/work/ecfb5994-9bb6-4f81-ad34-04e9f17496e7/7c5896d1-217b-48e5-9b47-6ebeff28c2c2/README.md).\n\nValidation passed:\n\n- `forge build`\n- `forge test`: 82 tests, including 8,192 invariant calls using the supplied seed\n- `forge fmt --check`\n- Local deployment rehearsal\n\nReady for the IMD launch service; no mainnet transaction was broadcast. The owner configures `passSigner` after launch.","treeHash":"9c1c0d409ae86170dbd2cbc03563b8f4bbc08c2b","usage":{"cachedInputTokens":1495808,"inputTokens":57887,"model":"gpt-6-astra","outputTokens":55830,"runtime":"codex","turns":10,"wallClockMs":1550935}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ee9fbaf2480d1034","findings":[{"citation":"resolved","description":"The brief uses the same words, 'not expired', for the deal expiry and for the holder pass expiry, but the code treats the boundary differently. A deal is expired when block.timestamp >= deal.expiry (line 185: `if (block.timestamp >= deal.expiry) revert DealExpired(dealId);`), so a buy in the block whose timestamp equals the deal's expiry is refused. A pass is still accepted when block.timestamp == passExpiry, because line 342 only rejects `expiry < block.timestamp`. The same `uint64 expiry` value therefore means 'last valid second' for passes and 'first invalid second' for deals. The test suite (test/TokenDealsPasses.t.sol test_passExpiryExactNowAnd24HourBoundaries) locks the inclusive pass behaviour in, so this is a deliberate choice, but it is inconsistent with the deal expiry check in the same contract and is the kind of off-by-one that off-chain signers and front ends get wrong (a pass signed to 'expire at T' is still redeemable at T for the discounted fee). Impact is limited to one second of extra validity on a fee discount. Fix: use `expiry <= block.timestamp` in _validatePass (exclusive, matching deals) or document the inclusive semantics for the signer, so both expiries follow one rule.","line":342,"path":"src/TokenDeals.sol","reproduction":"Owner calls setPassSigner(S). Seller opens deal 1 with expiry T = now + 100 (ETH, price 0.01 ether, minBuy 0). vm.warp(T). buy(1, 1e18, payer, 0.0101 ether, 0, \"\") with 0.0101 ether reverts DealExpired(1): expiry == now is expired. Seller then update(1, 0.01 ether, 0, now + 1 days). Sign HolderPass(payer, uint64(now)) with S and call buy(1, 1e18, payer, 0.01005 ether, uint64(now), pass) with 0.01005 ether: the buy succeeds at the holder fee, so passExpiry == now is valid. Expected: one convention for both; actual: deal expiry exclusive, pass expiry inclusive. Verified with test/scratch/MathEdges.t.sol test_expiryConventionsDiffer on this tree.","severity":"low","snippet":"        if (expiry < block.timestamp || uint256(expiry) > block.timestamp + MAX_PASS_LIFETIME) {","title":"Pass expiry and deal expiry use opposite boundary conventions at the exact timestamp"},{"citation":"resolved","description":"fee = floor(cost * bps / 10000) as the brief specifies, so with feeBps = 100 every buy whose cost is 1..99 base units pays no fee at all, and with the holder rate (50) any cost up to 199 units is fee-free. Because open and update allow minBuy = 0, a buyer can split a purchase into pieces that each cost under 100 units and pay no fee on any of them; the FeeSplitter loses at most 99 units per buy. On Ethereum mainnet the gas of each extra buy (about 100k gas) dwarfs the saving (99 wei, or $0.000099 in USDC), so this is not exploitable for profit and is reported for completeness of the math review, not as a defect. If the author wants a guaranteed non-zero fee, round the fee up (Math.Rounding.Ceil) or require cost >= 100 when a fee rate is set; both would change the stated rounding rule, so that is a scope decision.","line":334,"path":"src/TokenDeals.sol","reproduction":"Seller opens deal 1: sold token, payCoin ETH, price 1e12 (wei per 1e18 token units), minBuy 0. quote(1, 99e6, false) returns cost = ceil(99e6 * 1e12 / 1e18) = 99, fee = floor(99 * 100 / 10000) = 0, total = 99. buy{value: 99}(1, 99e6, payer, 99, 0, \"\") succeeds; seller.balance == 99 and FEE_TO.balance == 0. Expected by the stated rule: fee 0 (matches). Verified with test/scratch/MathEdges.t.sol test_zeroFeeMicroBuy on this tree.","severity":"info","snippet":"        fee = Math.mulDiv(cost, holder ? holderFeeBps : feeBps, 10_000);","title":"Fee rounds to zero for any buy whose cost is below 100 payment base units (200 with a pass)"},{"citation":"resolved","description":"price is an integer number of payment base units per 1e18 sold-token base units, as the brief defines. For USDC or USDT (6 decimals) the smallest positive price is 1, which is 0.000001 USDC per whole 18-decimal token, and the next price is 2, a 100% step. A token worth $0.0000015 cannot be priced within 33% in USDC, and a token worth $0.00001 has 10% price steps; for ETH (18 decimals) the resolution is 1 wei per whole token and this does not arise. Because cost rounds up, a buy of 1 wei of sold token on a price-1 USDC deal costs the same 1 unit as a buy of 1e18 wei; the buyer, not the seller, bears the rounding, and the seller cannot be underpaid. This is inherent in the price definition the brief chose and is reported so the author can document it for sellers of very low-value tokens; it is not a defect in the implementation.","line":333,"path":"src/TokenDeals.sol","reproduction":"Seller opens deal 1 with payCoin USDC (6 decimals) and price 1 (price 0 reverts InvalidPrice). quote(1, 1e18, false) returns cost 1 (0.000001 USDC for one whole token). quote(1, 1, false) also returns cost 1: 1 wei of token costs a full USDC base unit. There is no price between 1 and 2, so a seller wanting $0.0000015 per token must overprice by 33% or underprice by 33%. Verified with test/scratch/MathEdges.t.sol test_sixDecimalPriceGranularity on this tree.","severity":"info","snippet":"        cost = Math.mulDiv(amount, price, 1e18, Math.Rounding.Ceil);","title":"Price granularity in 6-decimal coins: the smallest price step is $0.000001 per whole 18-decimal token, and Ceil charges one full unit for any dust buy"}],"hash":"3e139c771e9419b34cd0d184143727098830ba1540ffdcbb9dd56c1a9b7ed3c7","nodeId":"75b1bc6d-bd48-4f02-b301-ea6b72117fa8","outcome":"completed","summary":"The findings file is valid, all three snippets match their cited lines, and the coverage record answers all 11 entry points plus three invariants. The scratch tests live under the gitignored test/scratch directory and no tracked file changed.\n\n**Outcome.** The math area of TokenDeals holds up. I found no medium or higher defect. The file `.imd-findings.json` holds one low and two info findings, each with a reproduction verified by a scratch Foundry test on this tree.\n\n**What I checked, against the Math Precision, Boundary and Numerical Gap guides:**\n- Cost uses OpenZeppelin mulDiv with Ceil and fee uses floor, exactly as the brief specifies. Overflowing products revert through checked arithmetic rather than wrapping. There are no narrowing casts of user values.\n- Sum of partial buys never underpays the seller, and the per-coin caps are checked on the rounded total in the correct base units for every default coin.\n- ETH and coin branches enforce exact msg.value, pull the total from the payer, and pay cost and fee separately. The live FeeSplitter on mainnet accepts ETH in about 220 gas of execution, so the 50k gas push cannot fail against current code.\n- Taxed sold tokens, no-bool tokens, donations and pay coins that are also escrowed all keep the escrow solvent. The existing suite of 82 tests, including the 128 by 64 invariant run, passes.\n\n**Findings written:**\n1. Low. Deal expiry is exclusive at the exact timestamp while pass expiry is inclusive, so a pass signed to expire at T still buys at the discounted fee at T.\n2. Info. Fee rounds to zero when cost is under 100 base units, and minBuy may be zero, but gas makes splitting uneconomic.\n3. Info. In 6-decimal coins the smallest price step is one millionth of a coin per whole token, which cannot price very low-value tokens finely.\n\n**Not reached.** I did not build a test for a buyer deliberately starving a contract seller's ETH push of gas via the 63/64 rule, because the brief defines a failed push as a credited debt, so that path is in-spec rather than a defect.","treeHash":null,"usage":{"cachedInputTokens":789836,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":33380,"runtime":"claude","turns":23,"wallClockMs":596305}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3a720671678111bc","findings":[{"citation":"resolved","description":"Economic Security / Invariant: buy pulls `total` of the payment coin, then pushes `cost` to the seller and `fee` to FEE_TO (lines 211-212) assuming exactly `total` arrived. The same coin can simultaneously be escrowed as the sold token of other deals (allowed by open: only token == payCoin of the same deal is refused), and all balances share one contract. If an allowlisted coin deducts a transfer fee, every buy paid in it receives less than `total` but still pays out `cost + fee`, so the shortfall is taken from unrelated sellers' escrow of that coin. The invariant 'for every token the contract holds at least the sum of left over its open deals' then breaks and the last seller to cancel or the last buyer to buy reverts, with the shortfall permanently stranded in Tether's fee wallet. USDT, allowlisted at deploy, has exactly this switch (basisPointsRate up to 20 bps, maximumFee) and no owner action on TokenDeals is needed for it to apply; the owner can also allowlist a taxed coin by mistake. The spec declares payment coins fee-free, so this is a hardening gap rather than a bypass, and it is griefable: anyone can self-trade a deal paid in the taxed coin to drain a victim seller's escrow at a 1:1 cost. Minimal fix that keeps the design: in buy, measure the payment coin balance before and after the pull and revert unless the delta equals `total` (or settle from the measured receipt).","line":204,"path":"src/TokenDeals.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TokenDeals} from \"src/TokenDeals.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @dev Plain ERC-20 with a Tether-style fee switch (basisPointsRate), off by default.\ncontract SwitchableFeeToken is ERC20 {\n    uint256 public basisPointsRate;\n\n    constructor() ERC20(\"Tether\", \"USDT\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n\n    function setParams(uint256 bps) external {\n        basisPointsRate = bps;\n    }\n\n    function _update(address from, address to, uint256 amount) internal override {\n        if (from != address(0) && to != address(0) && basisPointsRate != 0) {\n            uint256 fee = amount * basisPointsRate / 10_000;\n            super._update(from, address(0xfee), fee);\n            amount -= fee;\n        }\n        super._update(from, to, amount);\n    }\n}\n\ncontract PlainToken is ERC20 {\n    constructor() ERC20(\"Plain\", \"PLN\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice Fails on the current code: a buy paid in a coin that now taxes transfers settles cost and fee\n/// out of USDT escrowed by an unrelated seller. Passes once buy verifies the pulled amount (balance delta\n/// equals total) and reverts otherwise.\ncontract TaxedPayCoinEscrowTest is Test {\n    address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;\n    TokenDeals desk;\n    SwitchableFeeToken usdt;\n    PlainToken sold;\n    address sellerA = makeAddr(\"sellerA\");\n    address sellerB = makeAddr(\"sellerB\");\n    address buyer = makeAddr(\"buyer\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        desk = new TokenDeals();\n        SwitchableFeeToken impl = new SwitchableFeeToken();\n        vm.etch(USDT, address(impl).code);\n        usdt = SwitchableFeeToken(USDT);\n        sold = new PlainToken();\n    }\n\n    function test_buyInTaxedPayCoinMustNotConsumeOtherSellersEscrow() public {\n        // Seller A escrows 1000 USDT as the sold token of an ETH deal.\n        usdt.mint(sellerA, 1000e6);\n        vm.startPrank(sellerA);\n        usdt.approve(address(desk), 1000e6);\n        uint256 dealA = desk.open(USDT, 1000e6, address(0), 1e18, 0, uint64(block.timestamp + 1 days), address(0));\n        vm.stopPrank();\n        assertEq(usdt.balanceOf(address(desk)), 1000e6);\n\n        // Seller B sells a plain token for 1000 USDT.\n        sold.mint(sellerB, 1e18);\n        vm.startPrank(sellerB);\n        sold.approve(address(desk), 1e18);\n        uint256 dealB = desk.open(address(sold), 1e18, USDT, 1000e6, 0, uint64(block.timestamp + 1 days), address(0));\n        vm.stopPrank();\n\n        // The coin starts charging 0.1% per transfer (Tether's fee switch allows up to 0.2%).\n        usdt.setParams(10);\n\n        usdt.mint(buyer, 1010e6);\n        vm.startPrank(buyer);\n        usdt.approve(address(desk), 1010e6);\n        // Expected: the buy reverts (received 1008.99 != total 1010) or otherwise leaves A's escrow intact.\n        (bool ok,) = address(desk).call(abi.encodeCall(TokenDeals.buy, (dealB, 1e18, buyer, 1010e6, 0, \"\")));\n        vm.stopPrank();\n        ok;\n\n        (,,,, uint256 leftA,,,,,) = desk.deals(dealA);\n        assertGe(usdt.balanceOf(address(desk)), leftA, \"USDT escrow of seller A must stay fully backed\");\n\n        // Seller A must always be able to cancel and receive its full remainder.\n        vm.prank(sellerA);\n        desk.cancel(dealA);\n    }\n}","reproduction":"State: TokenDeals deployed; code at USDT behaves like Tether with basisPointsRate initially 0. 1) sellerA: usdt.approve(desk, 1000e6); open(USDT, 1000e6, address(0), 1e18, 0, now+1 day, 0) -> deal 1, desk USDT balance 1000e6, left 1000e6. 2) sellerB: open(plainToken, 1e18, USDT, 1000e6, 0, now+1 day, 0) -> deal 2. 3) Tether sets basisPointsRate = 10 (0.1%). 4) buyer: usdt.approve(desk, 1010e6); buy(2, 1e18, buyer, 1010e6, 0, \"\"). Expected: the buy reverts, or at least seller A's 1000e6 escrow stays fully backed. Actual: the buy succeeds; desk received 1008.99e6 but pushed 1000e6 to sellerB and 10e6 to FEE_TO, so desk USDT balance is 998.99e6 while deal 1 still has left = 1000e6. sellerA's cancel(1) then reverts (ERC20InsufficientBalance) and 1.01 USDT of sellerA's escrow is unrecoverable; repeating step 4 with self-trades drains more. Scratch test test/scratch/TaxedPayCoinEscrow.t.sol fails on the current code with 'USDT escrow of seller A must stay fully backed: 998990000 < 1000000000'.","severity":"low","snippet":"            IERC20(deal.payCoin).safeTransferFrom(msg.sender, address(this), total);","title":"buy settles a coin payment out of pooled escrow without checking the amount actually pulled; a payment coin that starts taxing transfers drains other sellers' escrow of that coin"},{"citation":"resolved","description":"Flow Gap (execution x first principles): the buyer's only protections are dealId and maxTotal; nothing in the call commits to the sold token, payment coin or price. Ids are assigned sequentially and never reused, so a deal that already exists cannot be swapped under a buyer (update cannot change token or payCoin, and cancel plus re-open yields a new id). But a buy that targets dealCount+1 while the seller's open is still pending, as a bridge or relayer settling a quoted purchase might submit, pays whoever lands the next open: an attacker who front-runs the honest open with a worthless token at the same price receives the full cost in ETH or coin, and the buyer gets junk within their maxTotal. The spec fixes the buy signature, so a fix changes the agreed ABI (add the expected token, payCoin and price, or a hash of them, and revert on mismatch); if the ABI must stay, integrators must only buy ids read from confirmed state and the docs should say so.","line":176,"path":"src/TokenDeals.sol","reproduction":"State: fresh TokenDeals, dealCount = 0. Honest seller broadcasts open(sold, 10e18, address(0), 0.01 ether, 0, exp, 0); the buyer broadcasts buy{value: 0.101 ether}(1, 10e18, buyer, 0.101 ether, 0, \"\") expecting id 1 to be that deal. Attacker front-runs: open(junk, 10e18, address(0), 0.01 ether, 0, exp, 0) -> returns id 1; the honest open then returns id 2. The buyer's buy(1, ...) succeeds: expected the buyer receives 10e18 of `sold` and the honest seller 0.1 ETH; actual the buyer receives 10e18 of `junk`, the attacker receives 0.1 ETH and FEE_TO 0.001 ETH. Reproduced in test/scratch/EconLeads.t.sol::test_pendingIdFrontRun.","severity":"low","snippet":"    function buy(\n        uint256 dealId,\n        uint256 amount,\n        address recipient,\n        uint256 maxTotal,\n        uint64 passExpiry,\n        bytes calldata pass\n    ) external payable nonReentrant whenNotPaused {","title":"buy binds only the deal id, so a buy submitted against a predicted (not yet confirmed) id can be front-run by an open() of a worthless token at the same price"},{"citation":"resolved","description":"Economic Security (dependency check, no defect today): claimOwed pays only msg.sender, so a credit to FEE_TO is recoverable only if the FeeSplitter itself can call claimOwed. The live FeeSplitter at 0x1119d5df2f046bc2d3c0f6279ef2e9ce49422d44 (Ethereum, read via the public mainnet RPC) is a keeper-driven distributor (ETH(), IMD(), keeper(), distribute(address), reserve(address), ethPool(address), unlockCallback(bytes), Ownable) with no arbitrary-call entry point. A plain 0.01 ETH send to it costs 21,220 gas total (about 220 gas in receive) and succeeds under a 50,000 gas limit, so ETH fees push successfully and nothing is credited. This is recorded so the operator knows that any future change making the splitter's receive revert or exceed 50k gas would strand every ETH fee in owed[FEE_TO] permanently; a TokenDeals fix would change the agreed claim design (e.g. let anyone trigger a push of owed[FEE_TO] to FEE_TO), so it is left as a note.","line":354,"path":"src/TokenDeals.sol","reproduction":"Not reproducible on current mainnet state: eth_estimateGas {from: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, to: 0x1119d5df2f046bc2d3c0f6279ef2e9ce49422d44, value: 0x2386f26fc10000} returns 0x52e4 (21,220) and eth_call with gas 0xc350 (50,000) succeeds. Hypothetical trigger: if receive() at FEE_TO reverted, buy{value: total} on an ETH deal would credit owed[FEE_TO] += fee (test_rejectingFeeSplitterAlsoGetsClaimableCredit shows the credit) and no on-chain path lets the splitter claim it.","severity":"info","snippet":"        (bool success,) = payable(account).call{value: amount, gas: ETH_PUSH_GAS}(\"\");\n        if (!success) {\n            owed[account] += amount;\n            emit OwedCredited(account, amount);","title":"Credited ETH fees could never be claimed by the FeeSplitter, which has no function to call claimOwed; not triggered today because its receive succeeds within the 50k gas push"}],"hash":"40663ed05cb690d9696b8d19baeef7994584441da6870393919d2ba293d5ad9e","nodeId":"f940ac45-aff3-4cc9-a6e0-c39a55b716d4","outcome":"completed","summary":"The review is complete. The economics of TokenDeals hold up well; I found no critical or high defects, and the findings file at the repository root records three items with a full coverage record.\n\n**What I checked**\n\n- Traced all eleven state-changing entry points against the Economic Security, Invariant and Flow Gap guides, plus the Senior Auditor's Mindset: value flows, token misbehaviour, conservation laws, cap and fee paths, view versus write agreement, and the seams between execution, periphery and intent.\n- Ran the project suite with the README seed. All 82 tests pass, including the stateful invariant campaign, and the format check on project files passes.\n- Verified the live dependencies on Ethereum mainnet: the FeeSplitter accepts a plain ETH send for roughly 220 gas inside the 50k push bound, and USDC, USDT, IMD and SHIFT report the decimals and symbols the caps assume.\n- Ruled out several leads after tracing them: fee-rounding evasion by splitting buys is dust-level, gas-starving the ETH push cannot leave enough gas to finish the buy under the 63/64 rule, and seller price raises are bounded by maxTotal and exact msg.value.\n\n**Findings written to .imd-findings.json**\n\n1. **Low, with proof.** A coin buy pulls the total and pushes cost plus fee assuming the full amount arrived. If an allowlisted coin starts taxing transfers, which USDT's fee switch can do without any owner action, each buy drains other sellers' escrow of that coin. The scratch proof fails on current code: the desk holds 998.99 USDT against 1,000 USDT of an unrelated open deal, and that seller's cancel reverts. A balance-delta check on the pull closes it.\n2. **Low.** The buy call commits only to the deal id. A buy submitted against a predicted id while the seller's open is pending can be front-run by an open of a worthless token at the same price. Reproduced in a scratch test. The fix touches the agreed ABI, so it is flagged as a scope decision.\n3. **Info.** The FeeSplitter is a keeper-driven distributor with no arbitrary-call function, so it could never claim credited ETH fees. Its receive currently succeeds cheaply, so nothing is credited today. Recorded for the operator.\n\n**Coverage.** Every listed entry point has a row: buy is marked finding, the other ten hold. Four extra rows cover the escrow, exact-payout and ETH-credit invariants and the mainnet dependency reads. No part of the assigned area was left unreached. Scratch tests live under test/scratch and are not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":1091726,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":42555,"runtime":"claude","turns":33,"wallClockMs":710641}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2bdd77125ec75e6c","findings":[{"citation":"resolved","description":"buy pulls `total` of the payment coin (line 204) and then pushes `cost` to the seller and `fee` to FEE_TO (lines 211-212) assuming exactly `total` arrived. The same coin may also be escrowed as the sold token of other deals (open only refuses token == payCoin of the same deal), and all balances share one contract. If an allowlisted coin deducts a transfer fee, every buy paid in it receives less than `total` but pays out `cost + fee`, so the shortfall is taken from unrelated sellers' escrow of that coin; the invariant 'the contract holds at least the sum of left over its open deals' breaks and the last seller to cancel reverts. USDT, allowlisted at deploy, has this switch (basisPointsRate up to 20 bps) and no TokenDeals owner action is needed for it to apply; the owner could also allowlist a taxed coin by mistake. The brief declares payment coins fee-free, so this is a hardening gap, not a bypass of a stated guarantee, hence low. Merged: audit_economics d1722408. Minimal fix that keeps the design: measure the payment-coin balance before and after the pull in buy and revert unless the delta equals `total`.","line":204,"path":"src/TokenDeals.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TokenDeals} from \"src/TokenDeals.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @dev Plain ERC-20 with a Tether-style fee switch (basisPointsRate), off by default.\ncontract SwitchableFeeToken is ERC20 {\n    uint256 public basisPointsRate;\n\n    constructor() ERC20(\"Tether\", \"USDT\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n\n    function setParams(uint256 bps) external {\n        basisPointsRate = bps;\n    }\n\n    function _update(address from, address to, uint256 amount) internal override {\n        if (from != address(0) && to != address(0) && basisPointsRate != 0) {\n            uint256 fee = amount * basisPointsRate / 10_000;\n            super._update(from, address(0xfee), fee);\n            amount -= fee;\n        }\n        super._update(from, to, amount);\n    }\n}\n\ncontract PlainToken is ERC20 {\n    constructor() ERC20(\"Plain\", \"PLN\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice Fails on the current code: a buy paid in a coin that now taxes transfers settles cost and fee\n/// out of USDT escrowed by an unrelated seller. Passes once buy verifies the pulled amount (balance delta\n/// equals total) and reverts otherwise.\ncontract TaxedPayCoinEscrowTest is Test {\n    address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;\n    TokenDeals desk;\n    SwitchableFeeToken usdt;\n    PlainToken sold;\n    address sellerA = makeAddr(\"sellerA\");\n    address sellerB = makeAddr(\"sellerB\");\n    address buyer = makeAddr(\"buyer\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        desk = new TokenDeals();\n        SwitchableFeeToken impl = new SwitchableFeeToken();\n        vm.etch(USDT, address(impl).code);\n        usdt = SwitchableFeeToken(USDT);\n        sold = new PlainToken();\n    }\n\n    function test_buyInTaxedPayCoinMustNotConsumeOtherSellersEscrow() public {\n        // Seller A escrows 1000 USDT as the sold token of an ETH deal.\n        usdt.mint(sellerA, 1000e6);\n        vm.startPrank(sellerA);\n        usdt.approve(address(desk), 1000e6);\n        uint256 dealA = desk.open(USDT, 1000e6, address(0), 1e18, 0, uint64(block.timestamp + 1 days), address(0));\n        vm.stopPrank();\n        assertEq(usdt.balanceOf(address(desk)), 1000e6);\n\n        // Seller B sells a plain token for 1000 USDT.\n        sold.mint(sellerB, 1e18);\n        vm.startPrank(sellerB);\n        sold.approve(address(desk), 1e18);\n        uint256 dealB = desk.open(address(sold), 1e18, USDT, 1000e6, 0, uint64(block.timestamp + 1 days), address(0));\n        vm.stopPrank();\n\n        // The coin starts charging 0.1% per transfer (Tether's fee switch allows up to 0.2%).\n        usdt.setParams(10);\n\n        usdt.mint(buyer, 1010e6);\n        vm.startPrank(buyer);\n        usdt.approve(address(desk), 1010e6);\n        // Expected: the buy reverts (received 1008.99 != total 1010) or otherwise leaves A's escrow intact.\n        (bool ok,) = address(desk).call(abi.encodeCall(TokenDeals.buy, (dealB, 1e18, buyer, 1010e6, 0, \"\")));\n        vm.stopPrank();\n        ok;\n\n        (,,,, uint256 leftA,,,,,) = desk.deals(dealA);\n        assertGe(usdt.balanceOf(address(desk)), leftA, \"USDT escrow of seller A must stay fully backed\");\n\n        // Seller A must always be able to cancel and receive its full remainder.\n        vm.prank(sellerA);\n        desk.cancel(dealA);\n    }\n}","reproduction":"State: TokenDeals deployed; USDT behaves like Tether with basisPointsRate 0. 1) sellerA: usdt.approve(desk, 1000e6); open(USDT, 1000e6, address(0), 1e18, 0, now+1d, 0) -> deal 1, desk USDT balance 1000e6, left 1000e6. 2) sellerB: open(plainToken, 1e18, USDT, 1000e6, 0, now+1d, 0) -> deal 2. 3) Tether sets basisPointsRate = 10. 4) buyer: usdt.approve(desk, 1010e6); buy(2, 1e18, buyer, 1010e6, 0, \"\"). Expected: the buy reverts or seller A's escrow stays fully backed. Actual: the buy succeeds; desk received 1008.99e6 but pushed 1000e6 to sellerB and 10e6 to FEE_TO, desk USDT balance is 998.99e6 while deal 1 still has left = 1000e6; sellerA's cancel(1) reverts with ERC20InsufficientBalance. Verified: test/scratch/Proof_d17224088826.t.sol fails on this tree with 'USDT escrow of seller A must stay fully backed: 998990000 < 1000000000'.","severity":"low","snippet":"            IERC20(deal.payCoin).safeTransferFrom(msg.sender, address(this), total);","title":"buy settles a coin payment out of pooled escrow without checking what was actually pulled; a payment coin that starts taxing transfers (USDT fee switch) drains other sellers' escrow of that coin"},{"citation":"resolved","description":"_payETH forwards at most ETH_PUSH_GAS = 50_000 gas. A seller contract whose receive() legitimately needs more (three cold SSTOREs, about 66k gas, as a splitter-style wallet might do) is not paid; its cost is silently moved to owed[seller] and the buy succeeds. claimOwed (line 246) pays only msg.sender, so the credit is recoverable only if that contract can itself issue an arbitrary call to TokenDeals; many cannot, and by design nobody else (owner included) can move the ETH, so it is stuck forever while the buyer keeps the tokens. The same applies to the fixed FEE_TO: its live code at 0x1119d5Df2f046bC2d3c0F6279EF2E9cE49422d44 (read via mainnet RPC: has code, a plain ETH send estimates 21,220 gas) accepts the push today and exposes no function that could call claimOwed, so any future revert or >50k receive there would strand every ETH fee in owed[FEE_TO] permanently. This is the behaviour the brief asks for on a failed push, so it is a documented limitation rather than a spec violation: the gas bound is a builder choice. Merged: audit_flow 431747d3 and audit_economics e455a8df (same root cause: owed[] credits are claimable only by the credited account). Fix options that keep the design: raise ETH_PUSH_GAS (e.g. 100k) and document that sellers must be EOAs or contracts able to call claimOwed; or add a permissionless push of owed[FEE_TO] to FEE_TO (a scope decision).","line":354,"path":"src/TokenDeals.sol","reproduction":"State: seller contract S whose receive() does count+=1 and writes two fresh mapping slots (~66k gas) and exposes no arbitrary-call function. S approves 10e18 TOKEN and opens open(TOKEN, 10e18, address(0), 0.01 ether, 0, now+30d, address(0)) -> deal 1. Payer calls buy{value: 0.101 ether}(1, 10e18, payer, 0.101 ether, 0, \"\"). Expected (brief: 'the full price to the seller'): S.balance == 0.1 ether. Actual: buy succeeds, S.balance == 0, owed[S] == 0.1 ether, desk.balance == 0.1 ether, payer holds 10e18 TOKEN; claimOwed() from any other account reverts NothingOwed. Control: a seller whose receive() writes one fresh slot (~44k gas) is paid 0.1 ether in full. Verified in test/scratch/JudgeChecks.t.sol::test_heavyReceiverCreditedLightReceiverPaid on this tree.","severity":"low","snippet":"        (bool success,) = payable(account).call{value: amount, gas: ETH_PUSH_GAS}(\"\");","title":"ETH credited to owed[] is unrecoverable for contract accounts that cannot call claimOwed, and the 50k-gas push bound makes that reachable for legitimate receivers (and for FEE_TO itself should its rec"},{"citation":"resolved","description":"The brief uses the same words, 'not expired', for both the deal and the holder pass. buy (line 185) treats a deal as expired when block.timestamp >= deal.expiry, but _validatePass (line 342) still accepts a pass when block.timestamp == passExpiry. Off-chain signers and front ends that assume one convention will issue passes redeemable one second longer than intended; impact is one second of extra fee discount, so low. test/TokenDealsPasses.t.sol locks the inclusive behaviour in, so if it is deliberate it needs documenting for the signer. Merged: audit_math f398b572. Fix: use `expiry <= block.timestamp` in _validatePass, or document the inclusive rule.","line":342,"path":"src/TokenDeals.sol","reproduction":"Owner setPassSigner(S). Seller opens deal 1 (ETH, price 0.01 ether, minBuy 0) with expiry T = now+100. vm.warp(T). buy(1, 1e18, payer, 0.0101 ether, 0, \"\") with 0.0101 ether reverts DealExpired(1). Seller update(1, 0.01 ether, 0, now+1 days). Sign HolderPass(payer, uint64(now)) with S; buy(1, 1e18, payer, 0.01005 ether, uint64(now), pass) with 0.01005 ether succeeds at the holder fee. Expected: one convention; actual: deal exclusive, pass inclusive. Verified in test/scratch/JudgeChecks.t.sol::test_expiryConventionsDiffer.","severity":"low","snippet":"        if (expiry < block.timestamp || uint256(expiry) > block.timestamp + MAX_PASS_LIFETIME) {","title":"Pass expiry is inclusive at the exact timestamp while deal expiry is exclusive, so the same uint64 means 'last valid second' for passes and 'first invalid second' for deals"},{"citation":"resolved","description":"The buyer's only protections are dealId and maxTotal; nothing in the call commits to the sold token, payment coin or price. Ids are sequential and never reused, and update cannot change token or payCoin, so an existing deal cannot be swapped under a buyer. But an integrator (bridge or relayer settling a quoted purchase) that submits buy(dealCount+1, ...) while the seller's open is pending pays whoever front-runs that open with a worthless token at the same price, within the buyer's maxTotal. The brief fixes the buy signature, so a code fix (committing to token, payCoin and price, or a hash of them) changes the agreed ABI; otherwise integrators must only buy ids read from confirmed state and the docs should say so. Reported as information because it needs buyer misuse. Merged: audit_economics 91d3ff45.","line":176,"path":"src/TokenDeals.sol","reproduction":"Fresh TokenDeals, dealCount 0. Attacker: open(junk, 10e18, address(0), 0.01 ether, 0, exp, 0) -> id 1; honest seller's open(sold, 10e18, address(0), 0.01 ether, 0, exp, 0) -> id 2. Buyer's pre-signed buy{value: 0.101 ether}(1, 10e18, buyer, 0.101 ether, 0, \"\") succeeds. Expected: buyer receives 10e18 sold and seller 0.1 ETH. Actual: buyer receives 10e18 junk, attacker 0.1 ETH, FEE_TO 0.001 ETH, seller 0. Verified in test/scratch/JudgeChecks.t.sol::test_pendingIdFrontRun.","severity":"info","snippet":"    function buy(","title":"buy commits only to the deal id, so a buy sent against a predicted, not yet confirmed id pays whoever lands the next open"},{"citation":"resolved","description":"The constructor stores caps in base units (1 ether, 2500e6, 500e18, 40_000_000e18) and setMaxPay writes max as base units, as its NatSpec says; maxPay(USDC) returns 2500000000, not 2500. Base units are the only workable choice since the contract does not know a later coin's decimals, so this is consistent, but the brief says 'in whole coins', and setMaxPay(USDC, 3000) blocks almost every USDC buy. Flagged for documentation to the owner. Merged: write_foundry_tests e30dd002.","line":293,"path":"src/TokenDeals.sol","reproduction":"Deal 1 sells 1e18 TOKEN for USDC at price 1_000_000 (1 USDC per token). vm.prank(owner); desk.setMaxPay(USDC, 3000); maxPay(USDC) == 3000. buy(1, 1e18, buyer, 2_000_000, 0, \"\") reverts PaymentCapExceeded (total 1_010_000 > 3000). Expected under a whole-coin reading: cap 3000 USDC, buy succeeds. Verified in test/scratch/JudgeChecks.t.sol::test_setMaxPayBaseUnits.","severity":"info","snippet":"    function setMaxPay(address coin, uint256 max) external nonReentrant onlyOwner {","title":"setMaxPay takes base units while the brief states caps in whole coins; an owner reading the brief literally sets a cap of 0.003 USDC"},{"citation":"resolved","description":"Only recipient == address(0) is rejected. With recipient == address(this) the payer pays cost plus fee and left is reduced, but the tokens stay in the contract where nobody can recover them (no sweep by design). Only the payer's own mistake is at stake and the brief does not forbid it; a cheap hardening is to also revert on recipient == address(this). Merged: write_foundry_tests b8606f21.","line":186,"path":"src/TokenDeals.sol","reproduction":"Deal 1: 10e18 TOKEN for ETH at 0.01 ether. buy{value: 0.101 ether}(1, 10e18, address(desk), 0.101 ether, 0, \"\") succeeds; desk TOKEN balance unchanged, deal.left == 0, open == false, seller received 0.1 ETH; the 10e18 TOKEN are unrecoverable. Verified in test/scratch/JudgeChecks.t.sol::test_recipientIsDeskStrandsTokens.","severity":"info","snippet":"        if (recipient == address(0) || (deal.buyer != address(0) && recipient != deal.buyer)) {","title":"buy accepts the desk itself as recipient, stranding the purchased tokens as unaccounted balance"},{"citation":"resolved","description":"fee = floor(cost * bps / 10000) as the brief specifies, so with feeBps 100 every buy costing 1..99 base units pays no fee, and a buyer on a deal with minBuy 0 can split into such pieces. On Ethereum mainnet each extra buy costs about 100k gas against a saving of at most 99 base units (99 wei, or $0.000099 in USDC), so it is not profitable; recorded for completeness. Changing it (round the fee up, or require cost >= 100) would change the stated rounding rule and is a scope decision. Merged: audit_math 1946ffd5.","line":334,"path":"src/TokenDeals.sol","reproduction":"Deal 1: sold token for ETH at price 1e12, minBuy 0. quote(1, 99e6, false) returns cost 99, fee 0, total 99. buy{value: 99}(1, 99e6, buyer, 99, 0, \"\") succeeds; seller.balance == 99, FEE_TO.balance unchanged. Verified in test/scratch/JudgeChecks.t.sol::test_zeroFeeMicroBuy.","severity":"info","snippet":"        fee = Math.mulDiv(cost, holder ? holderFeeBps : feeBps, 10_000);","title":"Fee rounds to zero for any buy whose cost is below 100 payment base units (200 with a pass), and minBuy = 0 lets a buyer split a purchase into fee-free pieces"}],"hash":"762767ed43a4a0ac94dda039a88a2efc6914557a18bf31fcadda5ef38ef607b5","nodeId":"826abcb2-fb7d-4d66-b121-fa38b78c7bf6","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` with 7 findings and 13 coverage rows. No tracked file was changed.\n\n**Outcome.** TokenDeals is sound. Nothing critical or high was found. All 102 project tests pass, the deployed runtime is 7,443 bytes with no reachable DELEGATECALL, CALLCODE or SELFDESTRUCT, the owner role matches the brief, and launch.json is schema-valid for the zero-argument constructor.\n\n**What I kept, after reproducing each against the code:**\n\n- **Low, with proof:** buy pulls `total` of the payment coin and pays out `cost + fee` without measuring what arrived. If an allowlisted coin starts taxing transfers (USDT has that switch), each buy drains other sellers' escrow of that coin. The economics specialist's proof fails on this tree as described, so it is attached.\n- **Low, merged from two specialists:** ETH credited to `owed[]` is only claimable by the credited account. The 50k gas push bound credits legitimate receivers that need about 66k gas, and such contracts often cannot call `claimOwed`. The live FeeSplitter accepts the push today, which I confirmed over mainnet RPC, but it has no way to claim either.\n- **Low:** pass expiry is inclusive at the exact timestamp while deal expiry is exclusive.\n- **Info:** buy commits only to the deal id, so buying a predicted id can be front-run by an open of junk at the same price. Info: `setMaxPay` takes base units while the brief says whole coins. Info: the desk itself is an allowed recipient, stranding tokens. Info: fees round to zero under 100 base units and `minBuy = 0` allows fee-free splitting.\n\n**Dropped:** the 6-decimal price granularity note, since it describes the brief's own price definition rather than a defect.\n\n**Coverage.** All 11 entry points answered: buy carries finding 1, the other ten hold. Two extra rows record the custody/opcode invariant and the manifest check.","treeHash":null,"usage":{"cachedInputTokens":710687,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":27873,"runtime":"claude","turns":18,"wallClockMs":623802}},{"artifacts":[],"attempt":1,"bundleHash":"ca58d938baa17d29985cf78ab318a94941e9984894fc0958f556d2625c46bb6c","device":"f96a0c89c039c22a","findings":[{"description":"The brief says the per-buy cap is 'in whole coins: ETH 1 and the caps above; setMaxPay(coin, max) changes a cap'. The constructor stores caps in base units (1 ether, 2500e6, 500e18, 40_000_000e18) and setMaxPay(coin, max) writes max as base units, so maxPay(USDC) returns 2500000000 rather than 2500. This is internally consistent, and base units are the only workable choice for coins added later with setPayCoin, because the contract does not know their decimals. But an owner who reads the brief literally and calls setMaxPay(USDC, 3000) sets a cap of 0.003 USDC, which blocks almost every USDC buy. Not asserted as a defect; flagged so the requester can confirm the unit and document it for the owner.","line":293,"path":"src/TokenDeals.sol","reproduction":"vm.prank(owner); desk.setMaxPay(USDC, 3000); a buy whose total is 1 USDC (1_000_000 base units) reverts with PaymentCapExceeded. Expected under a whole-coin reading: a cap of 3000 USDC and the buy succeeds. Actual: cap = 3000 base units.","severity":"info","title":"setMaxPay takes base units while the brief states caps in whole coins"},{"description":"buy only rejects recipient == address(0). With recipient == address(this), the payer pays cost plus fee and left is reduced, but the tokens stay in the contract as unaccounted balance that nobody can recover (by design there is no sweep). Only the payer's own mistake is at stake, and the brief does not forbid it, so no test asserts it either way.","line":186,"path":"src/TokenDeals.sol","reproduction":"buy(id, amount, address(desk), maxTotal, 0, '') succeeds; desk token balance is unchanged while deal.left drops by amount; the payer's tokens are unrecoverable. Expected: InvalidRecipient (a suggested hardening, not a spec requirement).","severity":"info","title":"buy accepts the desk itself as recipient, stranding the purchased tokens"}],"hash":"9d30abf8c6dd645407a251d69ccd1c7a6a6badd09a5e59427cd9797c53d83dd8","nodeId":"a08487cf-ff30-43e6-9a86-a4acc59887ab","outcome":"completed","summary":"I added two test files. `forge build` passes, and `forge test` with the verifier's seed passes all 102 tests in 9 suites (82 already there, 20 new). I found no defect that needs a failing proof.\n\nThe earlier contributor's suite already covered most of the brief, so I wrote tests for the gaps and committed them as `b759283`:\n\n**`test/TokenDealsEdges.t.sol`** (19 tests, 6 of them fuzz):\n- **Pricing:** a 6-decimal sold token is priced per 1e18 base units, as the brief says (5e6 units at 2 ETH cost 1e7 wei).\n- **Exact boundaries:**\n  - A buy one second before expiry works and one at expiry fails.\n  - A pass expiring 24 hours plus one second ahead is refused, and the same pass works one second later.\n  - When `minBuy` equals what's left, only the rest can be bought.\n  - The leftover below `minBuy` after odd partial buys can still be bought.\n  - Deal ids keep counting up across cancels and fills.\n- **Fees:**\n  - At 0% fee, nothing goes to the FeeSplitter.\n  - The 2% maximum settles exactly at an odd price.\n  - The cap counts the reduced holder total, so a pass can fit a buy under the cap that would otherwise be refused.\n- **Relayer buying for a user:** a relayer pays USDC for a private deal using the recipient's pass. The relayer's own pass and the relayer as recipient are both refused.\n- **Reentrancy from a seller:** while being paid in ETH, a seller contract tries cancel, update, buy and `claimOwed`. All four are blocked. Then it rejects ETH, gets credited, and claims the credit.\n- **Fuzz:**\n  - Splitting a buy never lowers the seller's cost.\n  - The holder fee is never above the normal fee or 2%.\n  - Invalid fee settings are always refused.\n  - A deposit of a token with a 2% transfer tax holds exactly what arrived.\n  - USDT buys for another recipient pay exactly cost and fee, with the cap and `maxTotal` checked.\n  - Any wrong `msg.value` is refused.\n\n**`test/TokenDealsConservation.t.sol`:** a second invariant suite with its own handler (256 runs, 16,384 calls, no unexpected reverts). It keeps running totals across ETH, USDC and USDT, a plain token and a 2%-tax token, a seller that rejects ETH, relayers and passes. During the run the owner changes fees, pauses and changes caps. It checks that:\n- Sellers' balances plus what they're owed equal the total cost of every buy, and the FeeSplitter's equal the total fees.\n- The contract never keeps USDC or USDT, and its ETH equals the total owed.\n- Each token's escrow equals deposits minus sales minus cancels, which equals the sum of `left` over open deals.\n- Closed deals never reopen.\n- Calls that must fail (wrong `maxTotal`, wrong ETH, a stranger or the owner cancelling, buying when paused or closed) never succeed and move nothing.\n\nThe first invariant run failed because of a bug in my handler, not the contract: after partial buys, `left` can drop below `minBuy`, which is valid. I fixed the handler.\n\nI checked the live FeeSplitter through a public RPC. Receiving ETH costs about 220 gas beyond the 21,000 base, so the contract's 50,000-gas forward to it is safe.\n\nI wrote two informational items to `.imd-findings.json` (it's git-excluded, so it isn't in the commit). Neither is asserted in a test:\n1. **Cap units:** `setMaxPay` takes base units, but the brief describes caps in whole coins. Read literally, `setMaxPay(USDC, 3000)` sets a cap of 0.003 USDC. The requester should confirm the unit and document it for the owner.\n2. **Desk as recipient:** `buy` accepts the contract itself as recipient. The payer pays, but the tokens stay stuck in the contract. Only the payer's own mistake is at risk.\n\nThe machine had no git identity, so I committed under the repo's existing `IdentityMD Worker` identity for that one commit and left the global config unchanged.","treeHash":"94c4602518c37dd6c57a2f599fe6a3ce8d3e04b6","usage":{"cachedInputTokens":1455243,"inputTokens":39,"model":"claude-opus-5-5","outputTokens":39536,"runtime":"claude","turns":24,"wallClockMs":679899}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dc7103e773ea194a","findings":[],"hash":"baa1440d47b889f899817a5d7259e5be781852e0e27104cb1c20036f3dbe9364","nodeId":"a84bc442-32c3-463d-9a06-e0f217d20421","outcome":"completed","summary":"The permissions review of TokenDeals is complete. I found no substantiated defect in the Access Control, Trust Gap or Asymmetry areas, and `.imd-findings.json` holds an empty findings list with a coverage row for every one of the 11 entry points plus three invariant rows.\n\n**What I checked against the code**\n\n- **Access control map.** The owner is a compiled constant, so neither the factory nor the transaction origin ever holds it. The six owner functions carry the same guard. Seller-only paths check the recorded seller. There is no receive or fallback, so direct ETH reverts. No sweep, execute, approve, upgrade or ownership function exists in the ABI.\n- **Asymmetry pairs.** The ETH and coin branches of `buy` pay the seller the cost and the FeeSplitter the fee in the same order and both skip a zero fee. Inventory and open status are written before any external call in `buy` and `cancel`. Credit is zeroed before the payout in `claimOwed`. The `open` and `update` validation rules match for price and expiry, with `minBuy` bounded by actual receipt and by remaining inventory respectively.\n- **Trust gap seams.** Fee changes are bounded by the buyer's `maxTotal` and route to a constant recipient, so the owner cannot profit from them. Cap changes and pauses can only make buys revert, never move value, and sellers keep cancel and claims. The holder pass is recipient-bound and reusable within 24 hours by design.\n\n**Live checks on Ethereum mainnet**\n\n- The FeeSplitter accepts a plain ETH send for about 220 gas of execution, well inside the 50,000-gas push limit, so fees will not pile up in the owed ledger.\n- USDC, USDT, IMD and SHIFT have code, report the decimals the caps assume, and delivered exact amounts in a fork probe, including a cap-sized buy through a fresh desk. USDT returned no boolean and SafeERC20 handled it.\n- The owner address carries an EIP-7702 delegation designator. This does not affect the `onlyOwner` checks.\n\n**Local verification.** The existing suite passed: 81 unit and fuzz tests across six suites plus the three stateful invariants. My scratch fork probes were removed and nothing in the tree was changed besides the findings file.\n\n**Not reached in depth.** Math rounding, numerical boundaries and economics belong to the other specialists and I only touched them where they intersected with permissions. One documented trust assumption remains for the author to keep in mind: any coin added later through `setPayCoin` must be a plain ERC-20, since the payment side trusts the pulled amount while the sale side measures receipt.","treeHash":null,"usage":{"cachedInputTokens":1030984,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":33135,"runtime":"claude","turns":35,"wallClockMs":630988}},{"artifacts":[],"attempt":1,"bundleHash":"618f3416d41b89668f2cbf2c3fa958676aa0287e7ee4d5b651e3e7a4749537a2","device":"3d294003c7683307","findings":[],"hash":"c6d622a0cccdd0328552835201830600e596cc1eb226fa5f3bec32a1f31c3e61","nodeId":"2d284058-d642-478a-8acd-36c160513f38","outcome":"completed","summary":"Created [launch.json](/home/seat/.identitymd/work/ecfb5994-9bb6-4f81-ad34-04e9f17496e7/2d284058-d642-478a-8acd-36c160513f38/launch.json), matching `TokenDeals`’ zero-argument constructor and documenting signer setup.\n\nValidation passed: manifest structure, compiled ABI, `forge build`, and all 82 tests using the supplied fuzz seed. Only the manifest is included in the deliverable.","treeHash":"d49bc5221f12c4380e7515c43d195823dba9c12f","usage":{"cachedInputTokens":424192,"inputTokens":34172,"model":"gpt-6-astra","outputTokens":3477,"runtime":"codex","turns":4,"wallClockMs":161147}}],"verification":[{"checks":[{"durationMs":25734,"exitCode":0,"name":"build","output":"Compiling 51 files with Solc 0.8.26\nSolc 0.8.26 finished in 25.64s\nCompiler run successful!\n","passed":true},{"durationMs":10794,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 13 tests for test/TokenDealsPasses.t.sol:TokenDealsPassesTest\n[PASS] test_chainIdChangeInvalidatesCachedDomain() (gas: 503703)\n[PASS] test_domainMetadata() (gas: 15905)\n[PASS] test_emptyPassIgnoresExpiryAndUsesNormalFee() (gas: 389879)\n[PASS] test_expiredAndTooFarAheadPassRevert() (gas: 391346)\n[PASS] test_holderCoinSettlementAtOddPriceAndAmount() (gas: 613908)\n[PASS] test_holderPassUsesHalfPercentAndRecipientNotPayer() (gas: 450239)\n[PASS] test_malformedHighSAndInvalidVSignaturesRevert() (gas: 532745)\n[PASS] test_nonemptyPassBeforeSignerSetReverts() (gas: 295411)\n[PASS] test_passExpiryExactNowAnd24HourBoundaries() (gas: 562118)\n[PASS] test_passReusePermittedAcrossDealsAndPayersUntilExpiry() (gas: 806175)\n[PASS] test_signedExpiryCannotBeAltered() (gas: 349130)\n[PASS] test_signerRotationRevokesOldPassesAndZeroDisablesPasses() (gas: 724997)\n[PASS] test_wrongSignerRecipientChainAndContractRevert() (gas: 524638)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 3.76ms (6.84ms CPU time)\n\nRan 10 tests for test/TokenDealsAdmin.t.sol:TokenDealsAdminTest\n[PASS] testFuzz_feeBounds(uint256,uint256) (runs: 256, μ: 35807, ~: 35800)\n[PASS] test_allAdminFunctionsRejectNonOwner() (gas: 190570)\n[PASS] test_directETHRejectedAndForcedETHHasNoRecoveryPath() (gas: 68764)\n[PASS] test_feeBoundsRefusedWithoutChangingState() (gas: 134371)\n[PASS] test_feeChangeIsIncludedInBuyersMaxTotal() (gas: 500156)\n[PASS] test_newPaymentCoinWorksAfterAllowlisting() (gas: 1224242)\n[PASS] test_ownerCannotMoveEscrowOrDonationsOrRedirectFees() (gas: 693393)\n[PASS] test_repeatedPauseUnpauseRefusedAndAdminWorksWhilePaused() (gas: 285056)\n[PASS] test_setterEventsAndValues() (gas: 302640)\n[PASS] test_unsolicitedTokensNotCountedInNewDeal() (gas: 480337)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 6.50ms (5.62ms CPU time)\n\nRan 16 tests for test/TokenDealsAdversarial.t.sol:TokenDealsAdversarialTest\n[PASS] test_allStateChangingEntrypointsGuardedDuringOpen() (gas: 5296645)\n[PASS] test_bothRejectCreditsAccumulateAndCannotBeClaimedByOthers() (gas: 1895083)\n[PASS] test_claimReentrancyCannotWithdrawTwice() (gas: 1160076)\n[PASS] test_failedCoinPayoutRollsBackPullTokenDeliveryAndEarlierPayout() (gas: 1719782)\n[PASS] test_feeSplitterCannotReenterClaimDuringSettlement() (gas: 955821)\n[PASS] test_gasExhaustingSellerAndFeeSplitterCannotBlockSettlement() (gas: 1753653)\n[PASS] test_largeETHReturnDataDoesNotBreakSettlement() (gas: 1585043)\n[PASS] test_maliciousSoldTokenCannotReenterBuyOrCancel() (gas: 2060665)\n[PASS] test_paymentCoinCanAlsoBeEscrowedInOtherDealsWithoutLoss() (gas: 1047982)\n[PASS] test_paymentTokenCannotReenterDuringPullOrPayout() (gas: 1866289)\n[PASS] test_recipientHookCannotReenterAndSeesUpdatedInventory() (gas: 1624835)\n[PASS] test_rejectingFeeSplitterAlsoGetsClaimableCredit() (gas: 1033834)\n[PASS] test_rejectingSellerCreditedThenClaimsWhilePaused() (gas: 1302242)\n[PASS] test_sameSellerAndFeeRecipientReceiveBothPayoutsOrCredits() (gas: 1096541)\n[PASS] test_sellerCannotReenterCancellationDuringPartialBuy() (gas: 1191517)\n[PASS] test_zeroFeeSkipsFailingCoinFeeRecipient() (gas: 1305226)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 7.43ms (7.57ms CPU time)\n\nRan 3 tests for test/TokenDealsDeployment.t.sol:TokenDealsDeploymentTest\n[PASS] test_deploymentRehearsalUsesExactConfiguration() (gas: 4110743)\n[PASS] test_factoryDeploymentOnEmptyChainHasFixedOwnerAndNoArguments() (gas: 232946)\n[PASS] test_reachableRuntimeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 4721548)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.35ms (9.17ms CPU time)\n\nRan 32 tests for test/TokenDealsFlows.t.sol:TokenDealsFlowsTest\n[PASS] test_ETHRejectedForCoinDeal() (gas: 453278)\n[PASS] test_ETHValueMustBeExact() (gas: 442229)\n[PASS] test_SHIFTSettlement() (gas: 621195)\n[PASS] test_allDefaultCapsIncludeFee() (gas: 1601925)\n[PASS] test_amountAndRecipientValidation() (gas: 445746)\n[PASS] test_capExactBoundaryAndZeroDisablesCap() (gas: 905370)\n[PASS] test_coinCapEqualityAndUncappedSettlement() (gas: 748426)\n[PASS] test_dealExpiryExactly365DaysAllowed() (gas: 289202)\n[PASS] test_eighteenDecimalIMDSettlement() (gas: 621733)\n[PASS] test_failedPaymentPullLeavesInventoryUntouched() (gas: 544932)\n[PASS] test_failedTokenTransfersRollbackAllSettlement() (gas: 579648)\n[PASS] test_fullETHSettlementAndEvent() (gas: 497066)\n[PASS] test_initialConfiguration() (gas: 153367)\n[PASS] test_maxTotalIncludesFeeAndAllowsEquality() (gas: 458541)\n[PASS] test_missingApprovalAndBalanceAreAtomic() (gas: 198679)\n[PASS] test_noBoolSoldTokenSupported() (gas: 578090)\n[PASS] test_noBoolUSDTSettlement() (gas: 595506)\n[PASS] test_nothingArrivedReverts() (gas: 829016)\n[PASS] test_openEventAndImmutableInventory() (gas: 733508)\n[PASS] test_openValidation() (gas: 437668)\n[PASS] test_partialBuysAndRestBelowMinimum() (gas: 784524)\n[PASS] test_pauseBlocksOnlyOpenUpdateBuyAndCancelStillWorks() (gas: 1112738)\n[PASS] test_privateDealRestrictsRecipientNotPayer() (gas: 460513)\n[PASS] test_quoteIsArithmeticPreviewEvenForUnavailableAmount() (gas: 327042)\n[PASS] test_removingPaymentCoinOnlyStopsNewDeals() (gas: 1115407)\n[PASS] test_sellerCanRenewExpiredOpenDeal() (gas: 474472)\n[PASS] test_sixDecimalSettlement() (gas: 621257)\n[PASS] test_taxedDepositMinBuyUsesActualReceipt() (gas: 856889)\n[PASS] test_taxedSoldTokenHeldBuyAndCancel() (gas: 1371540)\n[PASS] test_unknownExpiredCancelledAndFilledDeals() (gas: 1098178)\n[PASS] test_updateAndCancelRequireSellerEvenForOwner() (gas: 478406)\n[PASS] test_updateValidatesRemainingAndProtectsBuyerWithMaxTotal() (gas: 923185)\nSuite result: ok. 32 passed; 0 failed; 0 skipped; finished in 16.70ms (11.27ms CPU time)\n\nRan 7 tests for test/TokenDealsMath.t.sol:TokenDealsMathTest\n[PASS] testFuzz_quoteMatchesIndependentArithmetic(uint128,uint128,bool) (runs: 256, μ: 259669, ~: 259646)\n[PASS] testFuzz_settlementExactPayouts(uint96,uint96,bool,bool) (runs: 256, μ: 567834, ~: 621894)\nLogs:\n  Bound result 6456\n  Bound result 1060533701\n\n[PASS] test_feeMultiplicationDoesNotOverflowWhenResultFits() (gas: 259324)\n[PASS] test_mulDivHandles512BitIntermediate() (gas: 259019)\n[PASS] test_oddAmountRoundsCostUpAndFeeDown() (gas: 444846)\n[PASS] test_oneBaseUnitAlwaysHasPositiveCostAndCanHaveZeroFee() (gas: 391486)\n[PASS] test_unrepresentableCostAndTotalRevert() (gas: 319722)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 16.70ms (23.35ms CPU time)\n\nRan 1 test for test/TokenDealsInvariant.t.sol:TokenDealsInvariantTest\n[PASS]\nTokenDealsInvariantTest invariants:\n[PASS] invariant_ETHBacksCreditsAndAllBuysSettleExactly\n[PASS] invariant_everyTokenBacksAllOpenDeals\n[PASS] invariant_heldAndLeftFollowLifecycle\n TokenDealsInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------+--------------+-------+---------+----------╮\n| Contract          | Selector     | Calls | Reverts | Discards |\n+===============================================================+\n| TokenDealsHandler | admin        | 1045  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | advanceTime  | 1085  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | buyDeal      | 1036  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | cancelDeal   | 988   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | claimCredits | 948   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | donate       | 1049  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | openDeal     | 1022  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | updateDeal   | 1019  | 0       | 0        |\n╰-------------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 3000000000000000000\n  Bound result 14873\n  Bound result 238992584\n  Bound result 729654557648403657\n  Bound result 54246733\n  Bound result 255\n  Bound result 863913205990544727272\n  Bound result 10\n  Bound result 7000000000000000000\n  Bound result 415027865722143054595\n  Bound result 10001\n  Bound result 370102241978845851563\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.67s (10.66s CPU time)\n\nRan 7 test suites in 10.67s (10.73s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":62,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TokenDeals.buy(uint256,uint256,address,uint256,uint64,bytes)\",\"TokenDeals.cancel(uint256)\",\"TokenDeals.claimOwed()\",\"TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address)\",\"TokenDeals.pause()\",\"TokenDeals.setFees(uint256,uint256)\",\"TokenDeals.setMaxPay(address,uint256)\",\"TokenDeals.setPassSigner(address)\",\"TokenDeals.setPayCoin(address,bool)\",\"TokenDeals.unpause()\",\"TokenDeals.update(uint256,uint256,uint256,uint64)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":15,\"README.md\":155,\"docs/VALIDATION.md\":63,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployTokenDeals.s.sol\":16,\"src/TokenDeals.sol\":360,\"test/TokenDealsAdmin.t.sol\":177,\"test/TokenDealsAdversarial.t.sol\":300,\"test/TokenDealsDeployment.t.sol\":70,\"test/TokenDealsFlows.t.sol\":486,\"test/TokenDealsInvariant.t.sol\":82,\"test/TokenDealsMath.t.sol\":110,\"test/TokenDealsPasses.t.sol\":186,\"test/helpers/TokenDealsFixture.sol\":148,\"test/invariant/TokenDealsHandler.sol\":230,\"test/mocks/Actors.sol\":96,\"test/mocks/Tokens.sol\":151},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1806,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/TokenDeals.sol:142: Reentrancy in TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address) (src/TokenDeals.sol#142-167):\n[high/medium] reentrancy-eth at src/TokenDeals.sol:176: Reentrancy in TokenDeals.buy(uint256,uint256,address,uint256,uint64,bytes) (src/TokenDeals.sol#176-214):\n[medium/high] incorrect-equality at src/TokenDeals.sol:142: TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address) (src/TokenDeals.sol#142-167) uses a dangerous strict equality:\n[medium/medium] unused-return at src/TokenDeals.sol:338: TokenDeals._validatePass(address,uint64,bytes) (src/TokenDeals.sol#338-349) ignores return value by (recovered,err,None) = ECDSA.tryRecover(digest,pass) (src/TokenDeals.sol#346)\n[low/medium] missing-zero-check at src/TokenDeals.sol:279: TokenDeals.setPassSigner(address).signer (src/TokenDeals.sol#279) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/TokenDeals.sol:351: Reentrancy in TokenDeals._payETH(address,uint256) (src/TokenDeals.sol#351-359):\n[low/medium] reentrancy-benign at src/TokenDeals.sol:142: Reentrancy in TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address) (src/TokenDeals.sol#142-167):\n[low/medium] timestamp at src/TokenDeals.sol:176: TokenDeals.buy(uint256,uint256,address,uint256,uint64,bytes) (src/TokenDeals.sol#176-214) uses timestamp for comparisons\n[low/medium] timestamp at src/TokenDeals.sol:338: TokenDeals._validatePass(address,uint64,bytes) (src/TokenDeals.sol#338-349) uses timestamp for comparisons\n[low/medium] timestamp at src/TokenDeals.sol:315: TokenDeals._validateTerms(uint256,uint64) (src/TokenDeals.sol#315-320) uses timestamp for comparisons","passed":true},{"durationMs":446,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/TokenDeals.sol:246: ETH transferred without address checks\n[high] reentrancy-state-change at src/TokenDeals.sol:158: Reentrancy: State change after external call (2 places)\n[low] centralization-risk at src/TokenDeals.sol:270: Centralization Risk (6 places)\n[low] large-numeric-literal at src/TokenDeals.sol:31: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/TokenDeals.sol:125: Literal Instead of Constant (2 places)\n[low] local-variable-shadowing at src/TokenDeals.sol:38: Local Variable Shadows State Variable\n[low] state-no-address-check at src/TokenDeals.sol:280: Address State Variable Set Without Checks","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"05aa8d98b4a021236670f2dc57baa8efbaaf06bac4f1e293ce885eae132a7ba4","verifiedTreeHash":"9c1c0d409ae86170dbd2cbc03563b8f4bbc08c2b","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":35218,"exitCode":0,"name":"build","output":"Compiling 53 files with Solc 0.8.26\nSolc 0.8.26 finished in 35.14s\nCompiler run successful!\n","passed":true},{"durationMs":11219,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 13 tests for test/TokenDealsPasses.t.sol:TokenDealsPassesTest\n[PASS] test_chainIdChangeInvalidatesCachedDomain() (gas: 503703)\n[PASS] test_domainMetadata() (gas: 15905)\n[PASS] test_emptyPassIgnoresExpiryAndUsesNormalFee() (gas: 389879)\n[PASS] test_expiredAndTooFarAheadPassRevert() (gas: 391346)\n[PASS] test_holderCoinSettlementAtOddPriceAndAmount() (gas: 613908)\n[PASS] test_holderPassUsesHalfPercentAndRecipientNotPayer() (gas: 450239)\n[PASS] test_malformedHighSAndInvalidVSignaturesRevert() (gas: 532745)\n[PASS] test_nonemptyPassBeforeSignerSetReverts() (gas: 295411)\n[PASS] test_passExpiryExactNowAnd24HourBoundaries() (gas: 562118)\n[PASS] test_passReusePermittedAcrossDealsAndPayersUntilExpiry() (gas: 806175)\n[PASS] test_signedExpiryCannotBeAltered() (gas: 349130)\n[PASS] test_signerRotationRevokesOldPassesAndZeroDisablesPasses() (gas: 724997)\n[PASS] test_wrongSignerRecipientChainAndContractRevert() (gas: 524638)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 52.69ms (6.90ms CPU time)\n\nRan 10 tests for test/TokenDealsAdmin.t.sol:TokenDealsAdminTest\n[PASS] testFuzz_feeBounds(uint256,uint256) (runs: 256, μ: 35808, ~: 35800)\n[PASS] test_allAdminFunctionsRejectNonOwner() (gas: 190570)\n[PASS] test_directETHRejectedAndForcedETHHasNoRecoveryPath() (gas: 68764)\n[PASS] test_feeBoundsRefusedWithoutChangingState() (gas: 134371)\n[PASS] test_feeChangeIsIncludedInBuyersMaxTotal() (gas: 500156)\n[PASS] test_newPaymentCoinWorksAfterAllowlisting() (gas: 1224242)\n[PASS] test_ownerCannotMoveEscrowOrDonationsOrRedirectFees() (gas: 693393)\n[PASS] test_repeatedPauseUnpauseRefusedAndAdminWorksWhilePaused() (gas: 285056)\n[PASS] test_setterEventsAndValues() (gas: 302640)\n[PASS] test_unsolicitedTokensNotCountedInNewDeal() (gas: 480337)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 57.53ms (45.03ms CPU time)\n\nRan 32 tests for test/TokenDealsFlows.t.sol:TokenDealsFlowsTest\n[PASS] test_ETHRejectedForCoinDeal() (gas: 453278)\n[PASS] test_ETHValueMustBeExact() (gas: 442229)\n[PASS] test_SHIFTSettlement() (gas: 621195)\n[PASS] test_allDefaultCapsIncludeFee() (gas: 1601925)\n[PASS] test_amountAndRecipientValidation() (gas: 445746)\n[PASS] test_capExactBoundaryAndZeroDisablesCap() (gas: 905370)\n[PASS] test_coinCapEqualityAndUncappedSettlement() (gas: 748426)\n[PASS] test_dealExpiryExactly365DaysAllowed() (gas: 289202)\n[PASS] test_eighteenDecimalIMDSettlement() (gas: 621733)\n[PASS] test_failedPaymentPullLeavesInventoryUntouched() (gas: 544932)\n[PASS] test_failedTokenTransfersRollbackAllSettlement() (gas: 579648)\n[PASS] test_fullETHSettlementAndEvent() (gas: 497066)\n[PASS] test_initialConfiguration() (gas: 153367)\n[PASS] test_maxTotalIncludesFeeAndAllowsEquality() (gas: 458541)\n[PASS] test_missingApprovalAndBalanceAreAtomic() (gas: 198679)\n[PASS] test_noBoolSoldTokenSupported() (gas: 578090)\n[PASS] test_noBoolUSDTSettlement() (gas: 595506)\n[PASS] test_nothingArrivedReverts() (gas: 829016)\n[PASS] test_openEventAndImmutableInventory() (gas: 733508)\n[PASS] test_openValidation() (gas: 437668)\n[PASS] test_partialBuysAndRestBelowMinimum() (gas: 784524)\n[PASS] test_pauseBlocksOnlyOpenUpdateBuyAndCancelStillWorks() (gas: 1112738)\n[PASS] test_privateDealRestrictsRecipientNotPayer() (gas: 460513)\n[PASS] test_quoteIsArithmeticPreviewEvenForUnavailableAmount() (gas: 327042)\n[PASS] test_removingPaymentCoinOnlyStopsNewDeals() (gas: 1115407)\n[PASS] test_sellerCanRenewExpiredOpenDeal() (gas: 474472)\n[PASS] test_sixDecimalSettlement() (gas: 621257)\n[PASS] test_taxedDepositMinBuyUsesActualReceipt() (gas: 856889)\n[PASS] test_taxedSoldTokenHeldBuyAndCancel() (gas: 1371540)\n[PASS] test_unknownExpiredCancelledAndFilledDeals() (gas: 1098178)\n[PASS] test_updateAndCancelRequireSellerEvenForOwner() (gas: 478406)\n[PASS] test_updateValidatesRemainingAndProtectsBuyerWithMaxTotal() (gas: 923185)\nSuite result: ok. 32 passed; 0 failed; 0 skipped; finished in 57.15ms (9.47ms CPU time)\n\nRan 3 tests for test/TokenDealsDeployment.t.sol:TokenDealsDeploymentTest\n[PASS] test_deploymentRehearsalUsesExactConfiguration() (gas: 4110743)\n[PASS] test_factoryDeploymentOnEmptyChainHasFixedOwnerAndNoArguments() (gas: 232946)\n[PASS] test_reachableRuntimeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 4721548)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 57.42ms (9.29ms CPU time)\n\nRan 7 tests for test/TokenDealsMath.t.sol:TokenDealsMathTest\n[PASS] testFuzz_quoteMatchesIndependentArithmetic(uint128,uint128,bool) (runs: 256, μ: 259670, ~: 259653)\n[PASS] testFuzz_settlementExactPayouts(uint96,uint96,bool,bool) (runs: 256, μ: 567834, ~: 621894)\nLogs:\n  Bound result 6456\n  Bound result 1060533701\n\n[PASS] test_feeMultiplicationDoesNotOverflowWhenResultFits() (gas: 259324)\n[PASS] test_mulDivHandles512BitIntermediate() (gas: 259019)\n[PASS] test_oddAmountRoundsCostUpAndFeeDown() (gas: 444846)\n[PASS] test_oneBaseUnitAlwaysHasPositiveCostAndCanHaveZeroFee() (gas: 391486)\n[PASS] test_unrepresentableCostAndTotalRevert() (gas: 319722)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 58.29ms (107.78ms CPU time)\n\nRan 19 tests for test/TokenDealsEdges.t.sol:TokenDealsEdgesTest\n[PASS] testFuzz_USDTBuyForOtherRecipientPaysExactlyCostAndFee(uint256,uint256,bool,uint256) (runs: 500, μ: 647827, ~: 728612)\nLogs:\n  Bound result 588824942558444650298686\n  Bound result 130045727\n  Bound result 2\n\n[PASS] testFuzz_holderFeeNeverAboveNormalFeeOrMaximum(uint256,uint256,uint256,uint256) (runs: 1000, μ: 366261, ~: 366483)\nLogs:\n  Bound result 116\n  Bound result 0\n  Bound result 2\n  Bound result 1000000000000000000000000000000\n\n[PASS] testFuzz_invalidFeesNeverAccepted(uint256,uint256) (runs: 500, μ: 50829, ~: 50808)\n[PASS] testFuzz_splittingABuyNeverLowersTheSellersCost(uint256,uint256,uint256) (runs: 1000, μ: 336945, ~: 336912)\nLogs:\n  Bound result 1\n  Bound result 750303860512765201498510234172\n  Bound result 712467610382230183692509693514\n\n[PASS] testFuzz_taxedDepositHoldsExactlyWhatArrived(uint256) (runs: 500, μ: 1134604, ~: 1134724)\nLogs:\n  Bound result 21639\n\n[PASS] testFuzz_wrongETHValueAlwaysRejected(uint256,uint256) (runs: 500, μ: 358283, ~: 358450)\nLogs:\n  Bound result 1740466466\n  Bound result 4281\n\n[PASS] test_buyOneSecondBeforeExpiryWorksAndAtExpiryFails() (gas: 616260)\n[PASS] test_capCountsHolderTotalSoPassCanFitUnderCap() (gas: 555443)\n[PASS] test_claimOwedWithNothingOwedReverts() (gas: 70989)\n[PASS] test_dealIdsCountUpAcrossCancelsAndFills() (gas: 979347)\n[PASS] test_maxFeeTwoPercentSettlesExactly() (gas: 611977)\n[PASS] test_passSignedOneSecondPast24HoursRejected() (gas: 504968)\n[PASS] test_pausedBuyOpenUpdateRevertWithoutStateChange() (gas: 479890)\n[PASS] test_relayerBuysPrivateDealWithRecipientsPassInSixDecimalCoin() (gas: 748830)\n[PASS] test_restBelowMinBuyAfterOddPartial() (gas: 893979)\n[PASS] test_sellerReceivingETHCannotReenterAnythingAndIsCreditedWhenRejecting() (gas: 1322013)\n[PASS] test_sixDecimalSoldTokenIsPricedPer1e18BaseUnitsNotPerWholeToken() (gas: 1147984)\n[PASS] test_updatedMinBuyEqualToLeftOnlyAllowsTheRest() (gas: 685284)\n[PASS] test_zeroFeesSettleWithoutPayingFeeSplitter() (gas: 571058)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 58.01ms (328.57ms CPU time)\n\nRan 16 tests for test/TokenDealsAdversarial.t.sol:TokenDealsAdversarialTest\n[PASS] test_allStateChangingEntrypointsGuardedDuringOpen() (gas: 5296645)\n[PASS] test_bothRejectCreditsAccumulateAndCannotBeClaimedByOthers() (gas: 1895083)\n[PASS] test_claimReentrancyCannotWithdrawTwice() (gas: 1160076)\n[PASS] test_failedCoinPayoutRollsBackPullTokenDeliveryAndEarlierPayout() (gas: 1719782)\n[PASS] test_feeSplitterCannotReenterClaimDuringSettlement() (gas: 955821)\n[PASS] test_gasExhaustingSellerAndFeeSplitterCannotBlockSettlement() (gas: 1753653)\n[PASS] test_largeETHReturnDataDoesNotBreakSettlement() (gas: 1585043)\n[PASS] test_maliciousSoldTokenCannotReenterBuyOrCancel() (gas: 2060665)\n[PASS] test_paymentCoinCanAlsoBeEscrowedInOtherDealsWithoutLoss() (gas: 1047982)\n[PASS] test_paymentTokenCannotReenterDuringPullOrPayout() (gas: 1866289)\n[PASS] test_recipientHookCannotReenterAndSeesUpdatedInventory() (gas: 1624835)\n[PASS] test_rejectingFeeSplitterAlsoGetsClaimableCredit() (gas: 1033834)\n[PASS] test_rejectingSellerCreditedThenClaimsWhilePaused() (gas: 1302242)\n[PASS] test_sameSellerAndFeeRecipientReceiveBothPayoutsOrCredits() (gas: 1096541)\n[PASS] test_sellerCannotReenterCancellationDuringPartialBuy() (gas: 1191517)\n[PASS] test_zeroFeeSkipsFailingCoinFeeRecipient() (gas: 1305226)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 58.63ms (8.18ms CPU time)\n\nRan 1 test for test/TokenDealsConservation.t.sol:TokenDealsConservationTest\n[PASS]\nTokenDealsConservationTest invariants:\n[PASS] invariant_closedDealsStayClosedAndNoForbiddenCallSucceeds\n[PASS] invariant_deskKeepsNoPaymentsExceptOwedETH\n[PASS] invariant_feeSplitterReceivedExactlyCumulativeFee\n[PASS] invariant_sellersReceivedExactlyCumulativeCost\n[PASS] invariant_soldTokensConservedAndBackOpenDeals\n TokenDealsConservationTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+----------+-------+---------+----------╮\n| Contract            | Selector | Calls | Reverts | Discards |\n+=============================================================+\n| ConservationHandler | admin    | 2011  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | buy      | 4136  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | cancel   | 2062  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | claim    | 2094  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | open     | 1987  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | update   | 2027  | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| ConservationHandler | warp     | 2067  | 0       | 0        |\n╰---------------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 50000000000000000000\n  Bound result 3000000000000000\n  Bound result 0\n  Bound result 7000000000000000000\n  Bound result 50000000000000000000\n  Bound result 1\n  Bound result 0\n  Bound result 7000000000000000001\n  Bound result 50000000000000000000\n  Bound result 2\n  Bound result 0\n  Bound result 7000000000000000002\n  Bound result 26427735\n  Bound result 1\n  Bound result 7776000\n  Bound result 101000000\n  Bound result 999999999999999001765\n  Bound result 32718729991261125\n  Bound result 4000000000000000000\n  Bound result 11768\n  Bound result 1028\n  Bound result 999999999999999001853\n  Bound result 3833\n  Bound result 20002\n  Bound result 8814808\n  Bound result 8615\n  Bound result 3456\n  Bound result 6751\n  Bound result 18900230\n  Bound result 3591071\n  Bound result 256854\n  Bound result 4000000000000000002\n  Bound result 890830509375178859584\n  Bound result 999999999999999003253\n  Bound result 10831\n  Bound result 101\n  Bound result 100\n  Bound result 2534\n  Bound result 12345678\n  Bound result 23100336077316234507\n  Bound result 1010000\n  Bound result 24576\n  Bound result 3823\n  Bound result 750\n  Bound result 999999999999999010002\n  Bound result 3349\n  Bound result 923407246446771867556\n  Bound result 2\n  Bound result 18446744073709551615\n  Bound result 1\n  Bound result 8465742\n  Bound result 100000000\n  Bound result 1889567282\n  Bound result 18446744073709551615\n  Bound result 8873\n  Bound result 47995603126977237167\n  Bound result 11531529044847206\n  Bound result 9803520384375931785\n  Bound result 2389\n  Bound result 82296\n  Bound result 999999999999999012469\n  Bound result 100000000\n  Bound result 6\n  Bound result 889637239881023109944\n  Bound result 4534713945\n  Bound result 130056009638473364\n  Bound result 82347774995685865132\n  Bound result 92404003\n  Bound result 797\n  Bound result 973701016479338376686\n  Bound result 100000000\n  Bound result 59328778\n  Bound result 999999999999999003420\n  Bound result 253\n  Bound result 15082465\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.34s (9.32s CPU time)\n\nRan 1 test for test/TokenDealsInvariant.t.sol:TokenDealsInvariantTest\n[PASS]\nTokenDealsInvariantTest invariants:\n[PASS] invariant_ETHBacksCreditsAndAllBuysSettleExactly\n[PASS] invariant_everyTokenBacksAllOpenDeals\n[PASS] invariant_heldAndLeftFollowLifecycle\n TokenDealsInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------+--------------+-------+---------+----------╮\n| Contract          | Selector     | Calls | Reverts | Discards |\n+===============================================================+\n| TokenDealsHandler | admin        | 1045  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | advanceTime  | 1085  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | buyDeal      | 1036  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | cancelDeal   | 988   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | claimCredits | 948   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | donate       | 1049  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | openDeal     | 1022  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | updateDeal   | 1019  | 0       | 0        |\n╰-------------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 8814808\n  Bound result 14873\n  Bound result 238992584\n  Bound result 729654557648403657\n  Bound result 54246733\n  Bound result 96\n  Bound result 863913205990544727272\n  Bound result 6000000000000000000\n  Bound result 4000000000000000000\n  Bound result 415027865722143054595\n  Bound result 50000000000000000\n  Bound result 370102241978845851563\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.11s (11.10s CPU time)\n\nRan 9 test suites in 11.11s (20.84s CPU time): 102 tests passed, 0 failed, 0 skipped (102 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TokenDeals.buy(uint256,uint256,address,uint256,uint64,bytes)\",\"TokenDeals.cancel(uint256)\",\"TokenDeals.claimOwed()\",\"TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address)\",\"TokenDeals.pause()\",\"TokenDeals.setFees(uint256,uint256)\",\"TokenDeals.setMaxPay(address,uint256)\",\"TokenDeals.setPassSigner(address)\",\"TokenDeals.setPayCoin(address,bool)\",\"TokenDeals.unpause()\",\"TokenDeals.update(uint256,uint256,uint256,uint64)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":15,\"README.md\":155,\"docs/VALIDATION.md\":63,\"foundry.toml\":23,\"remappings.txt\":2,\"script/DeployTokenDeals.s.sol\":16,\"src/TokenDeals.sol\":360,\"test/TokenDealsAdmin.t.sol\":177,\"test/TokenDealsAdversarial.t.sol\":300,\"test/TokenDealsConservation.t.sol\":403,\"test/TokenDealsDeployment.t.sol\":70,\"test/TokenDealsEdges.t.sol\":415,\"test/TokenDealsFlows.t.sol\":486,\"test/TokenDealsInvariant.t.sol\":82,\"test/TokenDealsMath.t.sol\":110,\"test/TokenDealsPasses.t.sol\":186,\"test/helpers/TokenDealsFixture.sol\":148,\"test/invariant/TokenDealsHandler.sol\":230,\"test/mocks/Actors.sol\":96,\"test/mocks/Tokens.sol\":151},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9d30abf8c6dd645407a251d69ccd1c7a6a6badd09a5e59427cd9797c53d83dd8","verifiedTreeHash":"94c4602518c37dd6c57a2f599fe6a3ce8d3e04b6","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":24523,"exitCode":0,"name":"build","output":"Compiling 51 files with Solc 0.8.26\nSolc 0.8.26 finished in 24.45s\nCompiler run successful!\n","passed":true},{"durationMs":10688,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 16 tests for test/TokenDealsAdversarial.t.sol:TokenDealsAdversarialTest\n[PASS] test_allStateChangingEntrypointsGuardedDuringOpen() (gas: 5296645)\n[PASS] test_bothRejectCreditsAccumulateAndCannotBeClaimedByOthers() (gas: 1895083)\n[PASS] test_claimReentrancyCannotWithdrawTwice() (gas: 1160076)\n[PASS] test_failedCoinPayoutRollsBackPullTokenDeliveryAndEarlierPayout() (gas: 1719782)\n[PASS] test_feeSplitterCannotReenterClaimDuringSettlement() (gas: 955821)\n[PASS] test_gasExhaustingSellerAndFeeSplitterCannotBlockSettlement() (gas: 1753653)\n[PASS] test_largeETHReturnDataDoesNotBreakSettlement() (gas: 1585043)\n[PASS] test_maliciousSoldTokenCannotReenterBuyOrCancel() (gas: 2060665)\n[PASS] test_paymentCoinCanAlsoBeEscrowedInOtherDealsWithoutLoss() (gas: 1047982)\n[PASS] test_paymentTokenCannotReenterDuringPullOrPayout() (gas: 1866289)\n[PASS] test_recipientHookCannotReenterAndSeesUpdatedInventory() (gas: 1624835)\n[PASS] test_rejectingFeeSplitterAlsoGetsClaimableCredit() (gas: 1033834)\n[PASS] test_rejectingSellerCreditedThenClaimsWhilePaused() (gas: 1302242)\n[PASS] test_sameSellerAndFeeRecipientReceiveBothPayoutsOrCredits() (gas: 1096541)\n[PASS] test_sellerCannotReenterCancellationDuringPartialBuy() (gas: 1191517)\n[PASS] test_zeroFeeSkipsFailingCoinFeeRecipient() (gas: 1305226)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 8.90ms (7.18ms CPU time)\n\nRan 13 tests for test/TokenDealsPasses.t.sol:TokenDealsPassesTest\n[PASS] test_chainIdChangeInvalidatesCachedDomain() (gas: 503703)\n[PASS] test_domainMetadata() (gas: 15905)\n[PASS] test_emptyPassIgnoresExpiryAndUsesNormalFee() (gas: 389879)\n[PASS] test_expiredAndTooFarAheadPassRevert() (gas: 391346)\n[PASS] test_holderCoinSettlementAtOddPriceAndAmount() (gas: 613908)\n[PASS] test_holderPassUsesHalfPercentAndRecipientNotPayer() (gas: 450239)\n[PASS] test_malformedHighSAndInvalidVSignaturesRevert() (gas: 532745)\n[PASS] test_nonemptyPassBeforeSignerSetReverts() (gas: 295411)\n[PASS] test_passExpiryExactNowAnd24HourBoundaries() (gas: 562118)\n[PASS] test_passReusePermittedAcrossDealsAndPayersUntilExpiry() (gas: 806175)\n[PASS] test_signedExpiryCannotBeAltered() (gas: 349130)\n[PASS] test_signerRotationRevokesOldPassesAndZeroDisablesPasses() (gas: 724997)\n[PASS] test_wrongSignerRecipientChainAndContractRevert() (gas: 524638)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 15.46ms (6.69ms CPU time)\n\nRan 3 tests for test/TokenDealsDeployment.t.sol:TokenDealsDeploymentTest\n[PASS] test_deploymentRehearsalUsesExactConfiguration() (gas: 4110743)\n[PASS] test_factoryDeploymentOnEmptyChainHasFixedOwnerAndNoArguments() (gas: 232946)\n[PASS] test_reachableRuntimeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 4721548)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 19.69ms (11.19ms CPU time)\n\nRan 10 tests for test/TokenDealsAdmin.t.sol:TokenDealsAdminTest\n[PASS] testFuzz_feeBounds(uint256,uint256) (runs: 256, μ: 35807, ~: 35800)\n[PASS] test_allAdminFunctionsRejectNonOwner() (gas: 190570)\n[PASS] test_directETHRejectedAndForcedETHHasNoRecoveryPath() (gas: 68764)\n[PASS] test_feeBoundsRefusedWithoutChangingState() (gas: 134371)\n[PASS] test_feeChangeIsIncludedInBuyersMaxTotal() (gas: 500156)\n[PASS] test_newPaymentCoinWorksAfterAllowlisting() (gas: 1224242)\n[PASS] test_ownerCannotMoveEscrowOrDonationsOrRedirectFees() (gas: 693393)\n[PASS] test_repeatedPauseUnpauseRefusedAndAdminWorksWhilePaused() (gas: 285056)\n[PASS] test_setterEventsAndValues() (gas: 302640)\n[PASS] test_unsolicitedTokensNotCountedInNewDeal() (gas: 480337)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 19.76ms (9.81ms CPU time)\n\nRan 32 tests for test/TokenDealsFlows.t.sol:TokenDealsFlowsTest\n[PASS] test_ETHRejectedForCoinDeal() (gas: 453278)\n[PASS] test_ETHValueMustBeExact() (gas: 442229)\n[PASS] test_SHIFTSettlement() (gas: 621195)\n[PASS] test_allDefaultCapsIncludeFee() (gas: 1601925)\n[PASS] test_amountAndRecipientValidation() (gas: 445746)\n[PASS] test_capExactBoundaryAndZeroDisablesCap() (gas: 905370)\n[PASS] test_coinCapEqualityAndUncappedSettlement() (gas: 748426)\n[PASS] test_dealExpiryExactly365DaysAllowed() (gas: 289202)\n[PASS] test_eighteenDecimalIMDSettlement() (gas: 621733)\n[PASS] test_failedPaymentPullLeavesInventoryUntouched() (gas: 544932)\n[PASS] test_failedTokenTransfersRollbackAllSettlement() (gas: 579648)\n[PASS] test_fullETHSettlementAndEvent() (gas: 497066)\n[PASS] test_initialConfiguration() (gas: 153367)\n[PASS] test_maxTotalIncludesFeeAndAllowsEquality() (gas: 458541)\n[PASS] test_missingApprovalAndBalanceAreAtomic() (gas: 198679)\n[PASS] test_noBoolSoldTokenSupported() (gas: 578090)\n[PASS] test_noBoolUSDTSettlement() (gas: 595506)\n[PASS] test_nothingArrivedReverts() (gas: 829016)\n[PASS] test_openEventAndImmutableInventory() (gas: 733508)\n[PASS] test_openValidation() (gas: 437668)\n[PASS] test_partialBuysAndRestBelowMinimum() (gas: 784524)\n[PASS] test_pauseBlocksOnlyOpenUpdateBuyAndCancelStillWorks() (gas: 1112738)\n[PASS] test_privateDealRestrictsRecipientNotPayer() (gas: 460513)\n[PASS] test_quoteIsArithmeticPreviewEvenForUnavailableAmount() (gas: 327042)\n[PASS] test_removingPaymentCoinOnlyStopsNewDeals() (gas: 1115407)\n[PASS] test_sellerCanRenewExpiredOpenDeal() (gas: 474472)\n[PASS] test_sixDecimalSettlement() (gas: 621257)\n[PASS] test_taxedDepositMinBuyUsesActualReceipt() (gas: 856889)\n[PASS] test_taxedSoldTokenHeldBuyAndCancel() (gas: 1371540)\n[PASS] test_unknownExpiredCancelledAndFilledDeals() (gas: 1098178)\n[PASS] test_updateAndCancelRequireSellerEvenForOwner() (gas: 478406)\n[PASS] test_updateValidatesRemainingAndProtectsBuyerWithMaxTotal() (gas: 923185)\nSuite result: ok. 32 passed; 0 failed; 0 skipped; finished in 19.74ms (10.81ms CPU time)\n\nRan 7 tests for test/TokenDealsMath.t.sol:TokenDealsMathTest\n[PASS] testFuzz_quoteMatchesIndependentArithmetic(uint128,uint128,bool) (runs: 256, μ: 259669, ~: 259646)\n[PASS] testFuzz_settlementExactPayouts(uint96,uint96,bool,bool) (runs: 256, μ: 567834, ~: 621894)\nLogs:\n  Bound result 6456\n  Bound result 1060533701\n\n[PASS] test_feeMultiplicationDoesNotOverflowWhenResultFits() (gas: 259324)\n[PASS] test_mulDivHandles512BitIntermediate() (gas: 259019)\n[PASS] test_oddAmountRoundsCostUpAndFeeDown() (gas: 444846)\n[PASS] test_oneBaseUnitAlwaysHasPositiveCostAndCanHaveZeroFee() (gas: 391486)\n[PASS] test_unrepresentableCostAndTotalRevert() (gas: 319722)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 19.89ms (27.86ms CPU time)\n\nRan 1 test for test/TokenDealsInvariant.t.sol:TokenDealsInvariantTest\n[PASS]\nTokenDealsInvariantTest invariants:\n[PASS] invariant_ETHBacksCreditsAndAllBuysSettleExactly\n[PASS] invariant_everyTokenBacksAllOpenDeals\n[PASS] invariant_heldAndLeftFollowLifecycle\n TokenDealsInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------+--------------+-------+---------+----------╮\n| Contract          | Selector     | Calls | Reverts | Discards |\n+===============================================================+\n| TokenDealsHandler | admin        | 1045  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | advanceTime  | 1085  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | buyDeal      | 1036  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | cancelDeal   | 988   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | claimCredits | 948   | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | donate       | 1049  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | openDeal     | 1022  | 0       | 0        |\n|-------------------+--------------+-------+---------+----------|\n| TokenDealsHandler | updateDeal   | 1019  | 0       | 0        |\n╰-------------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 10000000000000000000\n  Bound result 10001\n  Bound result 0\n  Bound result 1000000000000000001\n  Bound result 3000000000000000000\n  Bound result 14873\n  Bound result 238992584\n  Bound result 729654557648403657\n  Bound result 54246733\n  Bound result 255\n  Bound result 863913205990544727272\n  Bound result 10\n  Bound result 7000000000000000000\n  Bound result 415027865722143054595\n  Bound result 10001\n  Bound result 370102241978845851563\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.59s (10.58s CPU time)\n\nRan 7 test suites in 10.59s (10.69s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TokenDeals.buy(uint256,uint256,address,uint256,uint64,bytes)\",\"TokenDeals.cancel(uint256)\",\"TokenDeals.claimOwed()\",\"TokenDeals.open(address,uint256,address,uint256,uint256,uint64,address)\",\"TokenDeals.pause()\",\"TokenDeals.setFees(uint256,uint256)\",\"TokenDeals.setMaxPay(address,uint256)\",\"TokenDeals.setPassSigner(address)\",\"TokenDeals.setPayCoin(address,bool)\",\"TokenDeals.unpause()\",\"TokenDeals.update(uint256,uint256,uint256,uint64)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":15,\"README.md\":155,\"docs/VALIDATION.md\":63,\"foundry.toml\":23,\"launch.json\":10,\"remappings.txt\":2,\"script/DeployTokenDeals.s.sol\":16,\"src/TokenDeals.sol\":360,\"test/TokenDealsAdmin.t.sol\":177,\"test/TokenDealsAdversarial.t.sol\":300,\"test/TokenDealsDeployment.t.sol\":70,\"test/TokenDealsFlows.t.sol\":486,\"test/TokenDealsInvariant.t.sol\":82,\"test/TokenDealsMath.t.sol\":110,\"test/TokenDealsPasses.t.sol\":186,\"test/helpers/TokenDealsFixture.sol\":148,\"test/invariant/TokenDealsHandler.sol\":230,\"test/mocks/Actors.sol\":96,\"test/mocks/Tokens.sol\":151},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c6d622a0cccdd0328552835201830600e596cc1eb226fa5f3bec32a1f31c3e61","verifiedTreeHash":"d49bc5221f12c4380e7515c43d195823dba9c12f","verifierVersion":"0.1.0+d372f99e"}]}