{"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":"1db0cc56-ac5e-4359-9576-aa8eb645906b","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"60efe9261a552468f22a05a8db70a5e27bd8479929ef3059c042c7eecff3550d","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"b9036fd7512bcb95b9095a9fbad9e787cb8344855e27f83eced326c58b97970f","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"b3d10c0e5a7b08356e4cb8a61d9954512568e78cd4ac9bc1ed304a1d959484c6","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"414113f161b823cc485fa9851c8966150affd064f80352aae97fc53e03ece46a","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"fcffbd333b5aeffd1504d81addfbde1c3f7653f20bfa4b578641345e7858090a","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"477e2100423db7487a605315259aba1ecd05b30d8a6f0e9edba531c47aa394ba","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"b39fdcc89af61a1d1e9c1b7250e4072b9ae4d702a07dcf55163e8844bbf2a4d1","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"1d9aea825047f8e2636bb84a7e8f00063b209e43e392e1398909940a1d7644ce","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"A custom token: HI (HI).\nToken name: HI\nToken symbol: HI\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\n\nTransfer rules: 1%","parentJobId":null,"planHash":"a5f5f7400a074e7e584bf0c4eb3a812c19d62c83c7e0e7b3b0642a945b279507","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"1db0cc56-ac5e-4359-9576-aa8eb645906b","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-780-hi"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51041","feedbackHash":"712e1074fafaaca453837920e139783d19792db195de3be1926ceecf70c1b31b","nodeKey":"audit_economics","submissionHash":"60efe9261a552468f22a05a8db70a5e27bd8479929ef3059c042c7eecff3550d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51070","feedbackHash":"72e3b02728eb3a6476550d8d56443402a1e29be527325fd00c4661c8ea0eb187","nodeKey":"audit_flow","submissionHash":"b9036fd7512bcb95b9095a9fbad9e787cb8344855e27f83eced326c58b97970f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51437","feedbackHash":"b4da3435d525254b110b7c498ffe23bb4a69331c65f046f274a5b75a72dcc94e","nodeKey":"audit_judge","submissionHash":"b3d10c0e5a7b08356e4cb8a61d9954512568e78cd4ac9bc1ed304a1d959484c6","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51141","feedbackHash":"0f610088db17162d1cb90f848eb4e4b85ef769de474657878794050dbaf88cd8","nodeKey":"audit_math","submissionHash":"414113f161b823cc485fa9851c8966150affd064f80352aae97fc53e03ece46a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51442","feedbackHash":"0e8566ef0d5aa4767fc20ca2fa4b9ad70911da1ebdb8d4103927690f073fb44e","nodeKey":"audit_permissions","submissionHash":"fcffbd333b5aeffd1504d81addfbde1c3f7653f20bfa4b578641345e7858090a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50907","feedbackHash":"3bf3748be99b22d2f71d6219251b1b9c84d28af93644e50a147c0fc800b726ed","nodeKey":"build_contract_project","submissionHash":"477e2100423db7487a605315259aba1ecd05b30d8a6f0e9edba531c47aa394ba","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51363","feedbackHash":"9ad9befd6d9941661320aa1dc62c205fbae2fa79c5bd09c5b7f27e79f17b5c49","nodeKey":"manifest","submissionHash":"b39fdcc89af61a1d1e9c1b7250e4072b9ae4d702a07dcf55163e8844bbf2a4d1","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52199","feedbackHash":"802492e25641fb17176fdf9894a9689ce7a4c5eb86a19cdf85d2f830cba9e8e2","nodeKey":"write_foundry_tests","submissionHash":"1d9aea825047f8e2636bb84a7e8f00063b209e43e392e1398909940a1d7644ce","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"19df65a13b53d5e4f09cf770d0d51f22069029134c1a75a6d099d5960eb9caed","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"eef919048a864a324029046013bf0df3adf4f915cd58abddbd623f96a51659e0","device":"410d15d57eeec440","findings":[{"description":"burnFor multiplies amount by 100 before dividing by 10,000. For amount > type(uint256).max / 100, the intermediate multiplication panics even though the correct rounded-down 1% result (amount / 100) fits in uint256. The public helper documents no input bound. This is limited to fee-query robustness: these amounts exceed the entire fixed supply, and transfers check the sender balance before evaluating the fee, so this finding does not demonstrate loss of funds or failure of a fundable transfer.","line":64,"path":"src/HIToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {HIToken} from \"src/HIToken.sol\";\n\ncontract BurnForOverflowProof is Test {\n    function test_burnForMaximumReturnsOnePercentRoundedDown() public {\n        HIToken token = new HIToken(address(this), address(0x9001), 7);\n        uint256 amount = type(uint256).max;\n        assertEq(token.burnFor(amount), amount / 100, \"1% fits in uint256 for every uint256 input\");\n    }\n}","reproduction":"Deploy HIToken(address(this), address(0x9001), 7), then call burnFor(type(uint256).max). Expected: type(uint256).max / 100. Actual: Solidity arithmetic panic 0x11. Reproduced with forge test --match-path test/scratch/BurnForOverflowProof.t.sol -vv (artifact and cache paths placed in test/scratch). The proof has no factory lookup because it only exercises the public pure helper.","severity":"low","title":"Public burnFor calculator overflows for large uint256 inputs"}],"hash":"1d9aea825047f8e2636bb84a7e8f00063b209e43e392e1398909940a1d7644ce","nodeId":"fbf0129f-6719-44ff-9239-eb477a12ffeb","outcome":"completed","summary":"Added failure-path, boundary, fuzz, and seven-actor invariant tests.\n\nOffline verification passed: **50 tests**, including **256 invariant sequences / 16,384 calls**.\n\nReported one low-severity defect in [.imd-findings.json](/home/imd/.identitymd/work/1db0cc56-ac5e-4359-9576-aa8eb645906b/fbf0129f-6719-44ff-9239-eb477a12ffeb/.imd-findings.json): `burnFor(uint256.max)` overflows. The report includes a reproduced failing proof; the input exceeds the entire token supply.","treeHash":"08533542e31ecebc2c55d846ee393266b2553f45","usage":{"cachedInputTokens":830464,"inputTokens":76152,"model":"gpt-6-astra","outputTokens":15408,"runtime":"codex","turns":6,"wallClockMs":408559}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7d454e6cee88c390","findings":[{"citation":"resolved","description":"Math Precision / zero-rounding: the fee is floor(amount * 100 / 10_000). Any amount below 100 minor units (1e-16 HI) burns nothing, and the fee on every larger amount is rounded in the sender's favour by up to 99 minor units. The Pashov precision guide expects fees to round up; here they round down. README line 73 documents this as intended ('Rounding favours the sender'). Economic impact is nil at 18 decimals: evading the burn on 1 HI needs about 1e16 transfers, each paying gas far above the 0.01 HI saved, so this is reported for completeness and not as a loss. If a strictly non-evadable fee is wanted, round up: (amount * BURN_BPS + BPS_DENOMINATOR - 1) / BPS_DENOMINATOR, which changes the documented 'amounts under 100 minor units burn nothing' behaviour and must be a design decision.","line":64,"path":"src/HIToken.sol","reproduction":"State: factory has moved 990 minor units to ALICE, no distributor recorded. Calls: ALICE calls transfer(BOB, 99) ten times. Expected under an exact 1% rule: 9 minor units burned (burnFor(990) == 9), BOB holds 981. Actual: burnFor(99) == 0 on each call, BOB holds 990, totalSupply unchanged at 1e27. Verified in a scratch Foundry test (test_dustSplitEvadesBurn): ten transfers of 99 deliver 990 with zero burn; a single transfer(BOB, 990) burns 9 and delivers 981.","severity":"info","snippet":"        return (amount * BURN_BPS) / BPS_DENOMINATOR;","title":"Burn fee rounds down to zero for transfers under 100 minor units, so the 1% can be split away"},{"citation":"resolved","description":"Boundary guide, external call corner cases: isExempt reaches this staticcall on every transfer that is not already exempt by the factory or PoolManager addresses. The decoded result is trusted as a 32-byte address. If the factory has no code (Solidity's extcodesize check reverts), reverts, or returns fewer than 32 bytes (ABI decoding reverts), every wallet-to-wallet transfer and every distributor claim reverts, while factory and PoolManager flows keep working because they short-circuit before the call. This is the behaviour the launch specification asks for (the distributor must be read from the factory at transfer time) and README lines 67-70 document it as a trust assumption on the launch infrastructure, so it is a documented liveness dependency rather than a code defect. There is no unprivileged trigger: only the factory's own code determines the answer. Recorded so the judge has the concrete failure modes; no change is proposed unless the author wants to cache a non-zero distributor after first sight, which would also stop an exempt address from ever being un-exempted.","line":51,"path":"src/HIToken.sol","reproduction":"State: a HIToken deployed by a factory whose distributorOf reverts (fallback revert('nope')) or returns 8 bytes (assembly return(0, 8)); the factory moved 100e18 to ALICE. Call: ALICE transfer(BOB, 1e18). Expected if the lookup were optional: BOB receives 0.99e18. Actual: the transfer reverts (with 'nope' for the reverting factory, with an ABI decoding failure for the short return). ALICE transfer(POOL_MANAGER, 1e18) still succeeds and moves the full 1e18. Verified in scratch Foundry tests (test_revertingFactoryBricksOrdinaryTransfers, test_shortReturnFactoryBricksOrdinaryTransfers); the project's own test_distributorLookupRevertsIfFactoryHasNoCode covers the no-code case.","severity":"info","snippet":"        return ILaunchFactory(factory).distributorOf(launchNumber);","title":"Every non-exempt transfer depends on a live factory.distributorOf call; a reverting or malformed answer blocks ordinary transfers"}],"hash":"414113f161b823cc485fa9851c8966150affd064f80352aae97fc53e03ece46a","nodeId":"2ccfcb7d-7497-4504-a19a-8d82097ac614","outcome":"completed","summary":"The review is complete. The findings file is in place and valid, the scratch tests are removed, and the tree is clean apart from the harness-excluded output file.\n\n**Result: no critical, high, medium or low defects in the token's math.** The 1% burn in `src/HIToken.sol` is arithmetically sound under every Math Precision, Boundary and Numerical Gap check I could reach, and the existing suite plus my own probes confirm it.\n\n**What I verified**\n\n- Overflow is unreachable through transfers. The insufficient-balance pre-check runs before the fee multiply, so the largest reachable operand is the whole supply, far below the overflow threshold. The public helper reverts on absurd input but is view-only.\n- Conservation holds exactly. Sender loses the full amount, recipient gains amount minus burn, supply drops by the burn. A multi-holder fuzz over random transfers never drifted.\n- Recipient always receives between 99% and 100% of the amount. Zero-amount and exact-balance transfers behave.\n- Launch flows are exempt as the floor requires. Factory and PoolManager checks short-circuit before the external call, the recorded distributor is exempt both ways, a zero or foreign-launch distributor exempts nothing.\n- Allowance accounting spends the full pre-fee amount, consistent with the sender paying the full amount.\n\n**Two info-level items recorded, each with a concrete input**\n\n1. The fee rounds down, so transfers under 100 minor units burn nothing and ten transfers of 99 wei deliver 990 with zero burn. The README documents this as intended, and the gas cost makes it economically meaningless at 18 decimals. The guide prefers fees that round up, so I noted the one-line change and its tradeoff.\n2. Every ordinary transfer depends on the factory answering the distributor lookup. A factory that reverts or returns malformed data blocks wallet-to-wallet transfers while exempt flows still work. This is what the launch specification requires and the README states it as a trust assumption. I reproduced both failure shapes.\n\n**Coverage.** All three entry points are marked `holds`, with additional rows for the overflow boundary, conservation, recipient-share bounds, exempt-flow exactness, the factory-call boundary, the \"1%\" interpretation, and the aderyn literal lead. No proof files were needed since nothing reached high severity.\n\nOne point for the requester outside the math: the brief's \"Transfer rules: 1%\" was read as a burn with no treasury. If a fee recipient or a per-transfer cap was intended, that is a scope decision rather than a defect.","treeHash":null,"usage":{"cachedInputTokens":862309,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":17715,"runtime":"claude","turns":25,"wallClockMs":232583}},{"artifacts":[],"attempt":1,"bundleHash":"5a3c01861a23a773ed2a0bba59b7e0036341ea0b682ef64aec0848c7fb5a7135","device":"5fd2de06561c50a4","findings":[],"hash":"477e2100423db7487a605315259aba1ecd05b30d8a6f0e9edba531c47aa394ba","nodeId":"4b12659b-95bd-4350-827e-4751c293f6f7","outcome":"completed","summary":"Taylor, the HI token is built, tested, documented, and committed on main as a442a04.\n\n**What was delivered**\n\n- `src/HIToken.sol`: OpenZeppelin ERC20, 1,000,000,000 HI with 18 decimals, minted once to the deployer in the constructor. No owner, mint, pause, blocklist, or upgrade path.\n- **Transfer rule:** 1% of every ordinary transfer is burned, so the recipient receives 99%. The factory, the Uniswap v4 PoolManager, and the launch's MerkleDistributor are exempt in both directions. The distributor is read live from the factory at transfer time, so the swarm share, claims, seed, and both swap directions move whole.\n- `script/DeployHI.s.sol`: local deploy script whose config function the tests call directly. The real launch deploys through the factory.\n- Tests: 29 passing across unit, fuzz with value conservation, failure paths, a sweep of admin selectors from a stranger and from the factory, and an opcode scan for DELEGATECALL, CALLCODE, and SELFDESTRUCT. A scratch rehearsal deploying via CREATE2 from a probe etched at the factory address also passed.\n- Dependencies vendored as plain files under `lib/` with no submodules. A clean build and the full suite passed with the network off.\n- `README.md` documents assumptions, constructor arguments, trust on the factory, operational responsibilities, and which security tools ran.\n\n**Checks**\n\n| Check | Result |\n|---|---|\n| forge build (solc 0.8.26, bytecode_hash none) | pass |\n| forge test | 29 passed |\n| forge fmt --check | pass |\n\n**Assumptions to confirm**\n\n- The brief's \"1%\" is read as a 1% fee burned on peer-to-peer transfers. No fee recipient was named, so burning avoided adding a privileged address. If a treasury was intended, the fee destination is one line in the contract's update hook.\n- Pool trades clear through the exempt PoolManager and are not taxed. The 1% applies to direct wallet-to-wallet movement.\n- Slither and Mythril were not available and did not run. Explorer verification after deployment is an open item for the network deployer.","treeHash":"0fc3c3d13804f951adb18df712ef24c6b338d733","usage":{"cachedInputTokens":766199,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":25953,"runtime":"claude","turns":25,"wallClockMs":305752}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3bed38612db34f32","findings":[{"citation":"resolved","description":"Economic Security guide, 'Break dependencies'. `_update` calls `isExempt`, which for any transfer that is not already factory/PoolManager-related performs an external view call to the immutable `factory` and ABI-decodes an address. `factory` cannot be changed and the result is never cached, so the token's ordinary transfers are coupled to the factory for the life of the token. Three concrete states turn every non-exempt transfer into a revert while exempt flows (factory, PoolManager) keep working: (a) the factory address has no code (call to a codeless address with a view interface reverts in Solidity 0.8 because of the extcodesize check); (b) the factory's function is named the same but takes a different width, e.g. `distributorOf(uint256)`, whose selector differs from `distributorOf(uint64)`; (c) the factory is upgraded or retired and stops answering. The brief says the token must call `distributorOf(launchNumber)` on the factory at transfer time, so the live read itself is by design, but the evidence that the deployed ProjectFactory exposes exactly `distributorOf(uint64) returns (address)` is not in the tree, and the protected floor cannot catch a selector mismatch because its probe defines the mapping as `mapping(uint64 => address)` itself. Who loses: every holder of HI who is not trading through the PoolManager (wallet-to-wallet transfers, CEX deposits, vesting payouts). Needed evidence: the ProjectFactory ABI on the target chain. Possible hardening that preserves the brief: cache the distributor the first time a non-zero value is read (one SSTORE), so a later factory outage only affects tokens whose distributor was never recorded; or wrap the read in a try/catch that treats a failed lookup as 'not exempt' rather than reverting. The second changes nothing for correct deployments and removes the permanent-brick mode.","line":51,"path":"src/HIToken.sol","reproduction":"State: HIToken deployed with factory_ = 0xFAC7 (no code), poolManager_ = 0x90A1, launchNumber_ = 7; deployer holds 1e27. Call transfer(0x90A1, 1e18) from the deployer: succeeds (to == poolManager). Call transfer(0xB0B, 1e18) from the deployer: expected a taxed transfer delivering 0.99e18; actual: reverts (staticcall to codeless factory). Same with a factory that defines only `mapping(uint256 => address) public distributorOf`: factory.move(A, 1000e18) succeeds, A.transfer(0x90A1, 1e18) succeeds, A.transfer(B, 1e18) reverts with empty returndata (unknown selector 0x...distributorOf(uint64)). Verified in test/scratch/Probe.t.sol::test_selectorMismatchBricksOrdinaryTransfers and ::test_factoryWithoutCodeBricksOrdinaryTransfers.","severity":"low","snippet":"        return ILaunchFactory(factory).distributorOf(launchNumber);","title":"Every ordinary transfer depends forever on a live staticcall to factory.distributorOf(uint64); if the factory has no code, reverts, or exposes a different selector, peer-to-peer transfers brick perman"},{"citation":"resolved","description":"Invariant guide, 'State couplings': the brief couples two facts, the whole supply is minted to msg.sender (the factory) and the factory is the exempt launch address. The constructor records `factory_` from an argument and mints to `msg.sender` without checking they agree. In the real launch the deployer resolves `$factory` to the factory that deploys, so the two coincide; but nothing in the token enforces it, and the project's own test `test_constructorMintsToWhoeverDeploys` documents the split. When they diverge: the address holding the supply is taxed 1% on every outgoing transfer (it is not exempt), the recorded `factory` is exempt while holding nothing, and if the recorded factory has no `distributorOf` every ordinary transfer reverts (finding 1). The protected floor catches the supply side (`balanceOf(factory) == expectedSupply`) only when it runs against the same arguments as the deployment. Minimal fix: `if (factory_ != msg.sender) revert`. Tradeoff: `script/DeployHI.s.sol::deploy` passes an arbitrary factory and would have to deploy through a factory-like contract (as `test/mocks/MockLaunchFactory.sol::deployToken` already does); the README says that script is review-only, so this is a small scope decision for the author.","line":40,"path":"src/HIToken.sol","reproduction":"State: EOA 0x1234 calls `new HIToken(0xFAC7, 0x90A1, 7)`. Expected (brief): the holder of the supply is the exempt factory. Actual: balanceOf(0x1234) == 1e27 and factory() == 0xFAC7. 0x1234.transfer(0xB0B, 1e18) is an ordinary transfer: with a live factory at 0xFAC7 it burns 0.01e18 (the launch share would arrive short); with no code at 0xFAC7 it reverts. Shown by the project's own test_constructorMintsToWhoeverDeploys and test_distributorLookupRevertsIfFactoryHasNoCode in test/HIToken.t.sol.","severity":"low","snippet":"        if (factory_ == address(0) || poolManager_ == address(0)) revert ZeroAddress();\n        factory = factory_;\n        poolManager = poolManager_;\n        launchNumber = launchNumber_;\n        _mint(msg.sender, TOTAL_SUPPLY);","title":"Constructor does not bind `factory_` to `msg.sender`, so a deployment whose factory argument is not the deployer mints the supply to one address and exempts another"},{"citation":"resolved","description":"Invariant guide, 'Interface guarantees / diverge view from write'. `isExempt` is meant to be the externally readable predicate for 'will this pair move whole?'. Because it folds `msg.sender == factory` into the answer, an off-chain or on-chain reader gets an answer about its own identity rather than about the transfer's eventual caller. The factory (or any contract reading on its behalf) sees `true` for every pair; everybody else sees the pair-only answer. Nothing in the launch calls this view, so there is no fund impact; it is an interface hazard for integrators who read it to decide whether to measure received balances. Fix without changing behaviour: expose the pair-only predicate as the public view and keep the `msg.sender == factory` clause inside `_update` (or pass the caller explicitly, e.g. `isExempt(address caller, address from, address to)`).","line":56,"path":"src/HIToken.sol","reproduction":"State: factory F deployed the token; F.move(A, 1000e18). Call `token.isExempt(A, B)` from F: returns true. Call `token.isExempt(A, B)` from anyone else: returns false. A then calls `transfer(B, 100e18)`: B receives 99e18, 1e18 burned. The view answered 'whole' to one reader and 'taxed' to another for the same transfer. Verified in test/scratch/Probe.t.sol::test_isExemptViewDivergesFromTransfer.","severity":"info","snippet":"        if (from == factory || to == factory || msg.sender == factory) return true;","title":"`isExempt(from,to)` is a public view whose answer depends on the caller's `msg.sender`, so it can report a different result than the transfer it describes"},{"citation":"resolved","description":"Flow Gap guide, seam execution x first principles. The code's end state is internally consistent (balances sum to totalSupply, supply only shrinks, launch flows are exact), but the economic effect differs materially between the plausible readings of the one-line brief: a 1% burn (value destroyed, benefits all holders pro rata), a 1% tax to a fee recipient (value accrues to the requester), or a 1% per-transfer / per-wallet cap (anti-whale, no value movement). The README records the burn choice and its reason (no recipient named, no admin needed). This is not a code defect; it is reported so the judge can confirm the reading before a change of fee destination becomes a post-launch impossibility (the token has no admin and no fee switch, so the destination cannot be changed after deployment).","line":77,"path":"src/HIToken.sol","reproduction":"State: factory moved 1000e18 to A. A calls transfer(B, 1000e18). Actual: B receives 990e18, totalSupply falls from 1e27 to 1e27 - 10e18, nobody receives the 10e18. If the requester intended a 1% fee to themselves, 10e18 that should have reached economics.remainderTo is gone, and no call can redirect future fees.","severity":"info","snippet":"        if (burned != 0) super._update(from, address(0), burned);","title":"The brief's 'Transfer rules: 1%' is implemented as a burn; the 1% is destroyed rather than paid to the requester or a treasury, which is a scope decision the requester should confirm"}],"hash":"60efe9261a552468f22a05a8db70a5e27bd8479929ef3059c042c7eecff3550d","nodeId":"f3148b6f-8fe0-4b91-8382-9dbcf483bc7f","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result. No source files were changed; only the findings file and my scratch tests under the ignored `test/scratch/` directory were written.\n\n**Outcome: no critical, high or medium defects.** The token's economics hold on a correct deployment. The 1% burn conserves value (balances always sum to totalSupply), the supply can only shrink, and every launch hop (factory to distributor, distributor claims, pool seed, remainder forwarding, and v4 buy and sell through the PoolManager) moves the full amount because one side of each is an exempt address. The burn cannot be used to tax or untax anyone else's transfer, and the only bypass routes are the pool itself (intended) and sub-100-wei dust chunks (gas dwarfs the saving).\n\n**Findings reported, all substantiated with scratch tests or the project's own tests:**\n\n1. **Low, permanent dependency on the factory lookup.** Every non-exempt transfer makes a live staticcall to `distributorOf(uint64)` on an immutable factory. A codeless factory, a reverting one, or one whose function takes `uint256` instead of `uint64` bricks all peer-to-peer transfers forever while pool and factory flows keep working. The protected floor cannot catch the selector case because its probe defines the same `uint64` mapping. The real ProjectFactory ABI is not in the tree, so I listed it as needed evidence.\n2. **Low, constructor does not require the factory argument to equal the deployer.** The supply goes to one address while a different one is exempt, so a mismatched deployment taxes the supply holder or reverts outright. Fix is a one-line check, with a small tradeoff for the review-only deploy script.\n3. **Info, view/write divergence.** The public `isExempt` folds in the caller's identity, so the factory sees every pair as exempt while everyone else sees the pair-only answer for the same transfer.\n4. **Info, interpretation of the brief.** \"Transfer rules: 1%\" was read as a burn. The fee is destroyed rather than paid to the requester, and with no admin the destination cannot change after deployment. Flagged for the judge to confirm the reading.\n\n**Coverage:** all three entry points (`approve`, `transfer`, `transferFrom`) are recorded as holds, plus rows for the conservation and supply-ceiling invariants, the full launch flow, and burn-bypass economics. One row is marked unreached: confirming the deployed factory's `distributorOf` signature, which only the chain or the factory source can answer.","treeHash":null,"usage":{"cachedInputTokens":713024,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":22988,"runtime":"claude","turns":26,"wallClockMs":338192}},{"artifacts":[],"attempt":1,"bundleHash":"543eb522cfdcf8fe66ee95ba71d6f1cb824a97d9ef1c21ef908c4dd91616b88e","device":"5c0fdae3b22cd896","findings":[],"hash":"b39fdcc89af61a1d1e9c1b7250e4072b9ae4d702a07dcf55163e8844bbf2a4d1","nodeId":"62c40b58-cc3b-41df-9f8d-4dd79d197f34","outcome":"completed","summary":"Created [launch.json](/home/seat/.identitymd/work/1db0cc56-ac5e-4359-9576-aa8eb645906b/62c40b58-cc3b-41df-9f8d-4dd79d197f34/launch.json) matching HIToken’s constructor, supply, and required economics. Only this file changed.\n\nManifest constraints and constructor ABI checks passed. `forge build` succeeded; `forge test` passed all 29 tests.","treeHash":"d69872d5c20c7a222944b91cd42c478bd42912e9","usage":{"cachedInputTokens":142720,"inputTokens":26503,"model":"gpt-6-astra","outputTokens":3019,"runtime":"codex","turns":3,"wallClockMs":88547}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"810692103659803b","findings":[{"citation":"resolved","description":"burnFor multiplies amount by 100 before dividing by 10,000. Inputs above type(uint256).max / 100 panic even though the rounded-down 1% result fits. This is a public fee-query defect, not a loss-of-funds issue: these amounts exceed the fixed supply and _update checks the gross balance before calculating the burn. For the fixed 1% rate, return amount / 100 to preserve rounding without overflowing.","line":64,"path":"src/HIToken.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {HIToken} from \"src/HIToken.sol\";\n\ncontract BurnForOverflowProof is Test {\n    function test_burnForMaximumReturnsOnePercentRoundedDown() public {\n        HIToken token = new HIToken(address(this), address(0x9001), 7);\n        uint256 amount = type(uint256).max;\n        assertEq(token.burnFor(amount), amount / 100, \"1% fits in uint256 for every uint256 input\");\n    }\n}","reproduction":"Deploy HIToken(address(this), address(0x9001), 7); call burnFor(type(uint256).max). Expected: type(uint256).max / 100. Actual: arithmetic panic 0x11 at the multiplication. Ran the supplied proof as test/scratch/BurnForOverflowProof.t.sol using forge test --offline --out test/scratch/out --cache-path test/scratch/cache --match-path test/scratch/BurnForOverflowProof.t.sol -vv: compilation succeeded and the sole test failed with panic 0x11.","severity":"low","snippet":"        return (amount * BURN_BPS) / BPS_DENOMINATOR;","title":"Public burnFor calculator overflows for large uint256 inputs"},{"citation":"resolved","description":"run() reads a uint256 environment value and narrows it to uint64 without a bounds check. Values above uint64.max wrap, silently binding the token to a different launch and distributor. This affects the documented local/fork review script; the manifest-based factory launch is a separate path. Read the value as uint256, require it to fit uint64, then cast.","line":24,"path":"script/DeployHI.s.sol","reproduction":"Set HI_FACTORY=0x000000000000000000000000000000000000fac7, HI_POOL_MANAGER=0x00000000000000000000000000000000000090a1 and HI_LAUNCH_NUMBER=18446744073709551623 (2^64 + 7). In a Foundry test, call new DeployHI().run(). Expected: reject an input that cannot fit the constructor uint64. Actual: deployment succeeds and token.launchNumber() is 7. Ran forge test --offline --out test/scratch/out --cache-path test/scratch/cache --match-path test/scratch/ScriptTruncation.t.sol -vv: the test expecting a revert failed with next call did not revert as expected; the companion test asserting launchNumber() == 7 passed. This was local test execution, with no network broadcast.","severity":"low","snippet":"            launchNumber: uint64(vm.envUint(\"HI_LAUNCH_NUMBER\"))","title":"Local deployment script silently truncates out-of-range launch numbers"},{"citation":"resolved","description":"The statement that transfers through other contracts are taxed and that the 1% applies to peer-to-peer movement is too broad. A permissionless PoolManager settle/take passthrough moves HI between ordinary holders without a swap and without burning. Both legs use the exemption at src/HIToken.sol:57. The exemption itself is required by the launch floor, so this is a documentation correction, not a recommendation to tax pool settlement or change the requested economics. Explicitly disclose that the 1% applies to non-exempt token calls and can be avoided by routing through the PoolManager. This merges the permissions and flow reports and lowers their severity: no unauthorized movement or direct fund loss was demonstrated.","line":49,"path":"README.md","reproduction":"Deploy through factory F with the configured Uniswap v4 PoolManager M and launch 7; F transfers 1000e18 HI to ALICE. ALICE approves helper P for 1000e18. P calls M.unlock(data); inside its callback P calls M.sync(HI), HI.transferFrom(ALICE, M, 1000e18), M.settle(), then M.take(HI, BOB, 1000e18). The deposit is exempt because to == poolManager; settle credits P +1000e18; take debits the same amount and calls HI.transfer from M, which is also exempt. All deltas end at zero, ALICE and P hold zero, BOB receives 1000e18, and totalSupply remains 1e27. A direct ALICE.transfer(BOB, 1000e18) instead gives BOB 990e18 and burns 10e18, as the existing ordinary-transfer test confirms. Expected under the quoted README claim: the peer-to-peer payment burns 10e18; actual: it burns zero. Reproduction is an exact source trace, not a locally executed real-PoolManager test. Independently checked unlock, sync, settle, take and _accountDelta in the primary source at https://raw.githubusercontent.com/Uniswap/v4-core/46c6834698c48bc4a463a86d8420f4eb1d7f3b75/src/PoolManager.sol; the repository test_poolManagerTransfersMoveWholeBothWays also executes both exempt token legs.","severity":"low","snippet":"settlement coming up short. Direct wallet-to-wallet transfers, and transfers through routers or\nother contracts, are taxed. Any Uniswap v4 trade clears through the PoolManager and is therefore\nuntaxed in both directions. The 1% applies to peer-to-peer movement, not to pool trading.","title":"README overstates the 1% burn guarantee for peer-to-peer transfers"}],"hash":"b3d10c0e5a7b08356e4cb8a61d9954512568e78cd4ac9bc1ed304a1d959484c6","nodeId":"cb853c18-8cdc-4b5e-81c4-704fbd794980","outcome":"completed","summary":"Wrote [`.imd-findings.json`](/home/imd-worker/.identitymd/work/1db0cc56-ac5e-4359-9576-aa8eb645906b/cb853c18-8cdc-4b5e-81c4-704fbd794980/.imd-findings.json) with three low-severity findings, concrete reproductions, and coverage of all three entry points.\n\nAll 50 existing tests passed. Two targeted defect checks failed as expected. Full protected launch integration was unavailable locally.","treeHash":null,"usage":{"cachedInputTokens":1219584,"inputTokens":118856,"model":"gpt-6-astra","outputTokens":8472,"runtime":"codex","turns":6,"wallClockMs":278940}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"89214b73ec1e0b7b","findings":[{"citation":"resolved","description":"Root cause: isExempt (src/HIToken.sol:55-60) treats any transfer whose sender OR recipient is the Uniswap v4 PoolManager as a launch flow and moves the full amount. The v4 PoolManager is a public singleton with flash accounting: inside unlock(), any contract may sync(token), transfer tokens in, call settle() to be credited the amount that arrived, and take(token, to, amount) to ANY recipient. No pool for HI needs to exist and no swap takes place. Both legs match the exemption (first to == poolManager, then from == poolManager), so the burn in _update never runs. One permissionless helper contract, deployed once by anyone and usable by any holder through approve(), turns the stated 1% rule into an opt-in. Measured cost: 79,404 gas for the passthrough versus 48,813 for a direct taxed transfer, i.e. about 30k gas (fractions of a cent to a few cents) replaces a 1% burn of any amount. A second variant never moves HI in the middle at all: pay in, manager.mint() ERC-6909 claims for HI, trade the claims inside the PoolManager (manager.transfer of the claim id), and later burn()+take() them out; a permanently tax-free wrapped HI exists by construction at the PoolManager. Consequence: the README's guarantees do not hold: line 13 ('1% of each ordinary transfer is burned; the recipient receives 99%'), lines 49-50 ('Direct wallet-to-wallet transfers, and transfers through routers or other contracts, are taxed.') and line 51 ('The 1% applies to peer-to-peer movement, not to pool trading'). The deflationary rule binds only users who do not route through the PoolManager. No party loses funds. Why this is not a one-line code fix: the launch floor requires the seed, buys and sells to move exactly what they say, and those flows pass through exactly these two legs (holder->PoolManager on a sell, PoolManager->holder on a buy). The token cannot distinguish a settle/take pair from a sell/buy pair, so narrowing the PoolManager exemption in either direction makes a trader's settlement come up short and the floor refuses the token. The bypass is inherent to taxing at the token layer while exempting a v4 singleton; the exemption itself is what the launch spec asks for. Needed scope decision from the requester: (a) accept the 1% as a default rather than an enforced rule and correct the README (drop the 'routers or other contracts are taxed' claim; disclose the PoolManager passthrough and the ERC-6909 wrap so integrators and holders are not misled), or (b) move the fee to a layer that cannot be routed around in this design, e.g. a 1% swap fee taken by a v4 hook with untaxed transfers, which changes the agreed transfer rule and pool configuration and is outside this token's scope. Reported as medium: an unprivileged actor breaks the stated guarantee at will, with no direct loss to an identifiable victim. No proof file is attached: the only fixes are a documentation correction or a design change, so no test can fail now and pass after a code fix without breaking the floor's swap requirement.","line":57,"path":"src/HIToken.sol","reproduction":"State: HIToken deployed by the factory with poolManager = the real Uniswap v4 PoolManager. I built the manager from v4-core commit 46c6834698c48bc4a463a86d8420f4eb1d7f3b75 with `new PoolManager(owner)`, solc 0.8.26, evm_version cancun; no pool was initialised for HI. The factory moves 1_000e18 HI to contract P, any contract implementing IUnlockCallback. Sequence: P calls manager.unlock(data). In P.unlockCallback: (1) manager.sync(Currency.wrap(address(token))); (2) token.transfer(address(manager), 1_000e18) -> _update sees to == poolManager, isExempt returns true at line 57, 1_000e18 arrives, nothing burned; (3) uint256 paid = manager.settle() -> paid == 1_000e18, P is credited +1_000e18; (4) manager.take(Currency.wrap(address(token)), BOB, paid) -> the PoolManager calls token.transfer(BOB, 1_000e18): from == poolManager, isExempt true, 1_000e18 arrives. unlock returns with all deltas zero. Expected under the stated rule, and what a direct `vm.prank(P); token.transfer(BOB, 1_000e18)` does (verified): BOB 990e18, totalSupply = SUPPLY - 10e18. Actual: BOB holds 1_000e18, totalSupply unchanged, manager and P hold 0. EOA variant: ALICE approve(P, 1_000e18); P does token.transferFrom(ALICE, address(manager), 1_000e18) in the callback (to == poolManager -> exempt) then settle/take as above -> identical result, so one shared helper serves every holder. ERC-6909 variant: after settle, manager.mint(P, uint256(uint160(address(token))), paid) instead of take; P moves the claims with manager.transfer(W2, id, 1_000e18) (no HI transfer at all); W2 later does manager.burn(W2, id, 1_000e18) + manager.take(token, W2, 1_000e18) -> W2 holds 1_000e18, totalSupply unchanged. Gas measured in the same run, warm slots: direct taxed transfer 48,813; passthrough 79,404. Harness I used (outside the repo because v4-core is not vendored): a foundry project with the repo's src/ and vendored OZ/forge-std, v4-core cloned at the commit above, remappings v4-core/=lib/v4-core/ and solmate/=lib/v4-core/lib/solmate/; tests test_passthroughViaRealPoolManagerSkipsTheBurn, test_anyHolderUsesOneSharedHelperWithApprove and test_erc6909WrappedHIIsTaxFreeForever all pass asserting the bypass; test_seedAndSwapEachWayAgainstRealPoolManager (factory single-sided seed, trader buys with 0.01 ETH, sells everything back) confirms the legitimate flows still move whole and burn nothing.","severity":"medium","snippet":"        if (from == poolManager || to == poolManager) return true;","title":"The 1% burn is optional: any holder routes a peer-to-peer transfer through the exempt Uniswap v4 PoolManager (settle/take or ERC-6909 claims) and nothing is burned"},{"citation":"resolved","description":"run() reads HI_LAUNCH_NUMBER as uint256 and narrows it with a bare uint64(...) cast. Explicit narrowing conversions are unchecked in Solidity 0.8: a value >= 2^64 wraps modulo 2^64 instead of reverting, so a mistyped or malformed environment value deploys a token whose immutable launchNumber points at a different launch's distributor, with no error. Scope: this script is the review-only local path (README, 'Local deployment'); the real launch deploys through ProjectFactory.launchCustom with the manifest's ABI-encoded $launchNumber, which cannot truncate. Impact is therefore limited to a fork or review deployment being bound to the wrong distributor silently, which misleads whoever reviews the exemptions on it. Minimal fix that preserves behaviour: read into a uint256, `require(n <= type(uint64).max, \"HI_LAUNCH_NUMBER exceeds uint64\")`, then cast, so the script refuses rather than wraps.","line":24,"path":"script/DeployHI.s.sol","reproduction":"Set HI_FACTORY=0x000000000000000000000000000000000000FAC7, HI_POOL_MANAGER=0x00000000000000000000000000000000000090A1, HI_LAUNCH_NUMBER=18446744073709551623 (= 2^64 + 7). Call `new DeployHI().run()`. Expected: revert, since the value does not fit the constructor's uint64 and no launch has that number. Actual: deploys, and token.launchNumber() == 7. Verified with a scratch test (test/scratch/ScriptTruncation.t.sol, not kept) that sets the three variables with vm.setEnv: `vm.expectRevert(); deployer.run();` fails with 'next call did not revert as expected', and `assertEq(deployer.run().launchNumber(), 7)` passes.","severity":"low","snippet":"            launchNumber: uint64(vm.envUint(\"HI_LAUNCH_NUMBER\"))","title":"DeployHI.run() silently truncates HI_LAUNCH_NUMBER to uint64 instead of refusing an out-of-range value"}],"hash":"b9036fd7512bcb95b9095a9fbad9e787cb8344855e27f83eced326c58b97970f","nodeId":"37cbbf7f-b163-4fc8-848f-59b939652cf1","outcome":"completed","summary":"Nothing further is needed — the file validates, both snippets match the tree byte-for-byte, all three listed entry points have rows, and no tracked file changed. The review is complete.\n\n## Summary\n\n**Scope covered:** Execution Trace, Periphery and First Principles against `src/HIToken.sol`, its interface, the vendored base contracts, and the deployment script. Build and all 29 existing tests pass. Findings are in `.imd-findings.json` (2 findings, 18 coverage rows, 100% of listed entry points answered).\n\n**Finding 1 — medium — the 1% burn is optional** (`src/HIToken.sol:57`).\n`isExempt` exempts any transfer with the PoolManager on either end. Uniswap v4's PoolManager is a public singleton with flash accounting, so inside `unlock()` anyone can `sync → transfer in → settle → take(to: anyone)` with no pool for HI and no swap. Both legs hit the exemption, so the burn never runs. I verified this against **real v4-core** (commit `46c6834`, built outside the repo since v4-core isn't vendored): BOB receives 1,000e18 with supply unchanged, versus 990e18 and a 10e18 burn on a direct transfer. It costs 79.4k gas versus 48.8k — about 30k gas replaces a 1% burn of any size. An ERC-6909 variant (settle + `mint` claims, trade claims, `burn` + `take`) yields a permanently tax-free wrapped HI. The README's claims at lines 13, 49–51 (\"transfers through routers or other contracts, are taxed\") are false. I was explicit that this is **inherent and spec-mandated**: the token can't tell settle/take from sell/buy, so narrowing the exemption breaks swaps and fails the floor. The honest fix is a requester decision — document the 1% as a default, or move the fee to a v4 hook (a design change). No proof file: no test can fail now and pass after a code fix without breaking the floor, so one would mislead.\n\n**Finding 2 — low — silent `uint64` truncation** (`script/DeployHI.s.sol:24`). `HI_LAUNCH_NUMBER = 2^64+7` deploys with `launchNumber == 7` instead of reverting. Review-only path, hence low; reproduced in a scratch test.\n\n**Verified clean:** supply minted exactly once to the factory and can't grow; balance/supply conservation; all five launch flows (swarm share, claim, seed, buy, sell) move whole against the real PoolManager; no privileged selectors; no DELEGATECALL/CALLCODE/SELFDESTRUCT; vendored OZ v5.1.0 and forge-std v1.9.7 are byte-identical to upstream at their pinned commits. I dropped one lead after inversion (the `msg.sender`-dependent `isExempt` view is self-consistent with the asker's own transfer).\n\n**Unreached:** the production `ProjectFactory.distributorOf` and `MerkleDistributor` sources aren't in the tree; the protected probe's getter matches the interface, and I recorded that as a trust assumption rather than a verified fact.\n\nScratch artifacts: `test/scratch/ScriptTruncation.t.sol` (gitignored, yours to discard) and the v4 harness at `/tmp/v4repro`.","treeHash":null,"usage":{"cachedInputTokens":941435,"inputTokens":327,"model":"claude-fable-5-1","outputTokens":60692,"runtime":"claude","turns":31,"wallClockMs":839661}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2b9b0095482c54e6","findings":[{"citation":"resolved","description":"Asymmetry / trust-gap (economics x asymmetry). The exemption treats any transfer whose sender OR recipient is the Uniswap v4 PoolManager as a launch flow and burns nothing. But the PoolManager is a permissionless contract: inside any `unlock` callback an arbitrary caller may `sync(HI)`, transfer HI to the manager, `settle()` to be credited the delta, and then `take(HI, recipient, amount)` to any recipient of their choosing, or `mint` ERC-6909 claim tokens that are then freely transferable outside the token entirely. Both legs hit the exempt branch (`to == poolManager` on the way in, `from == poolManager` on the way out), so a peer-to-peer transfer routed through the PoolManager is taxed 0% while the same transfer made directly is taxed 1%. No swap, pool, or liquidity is needed. README.md line 49-51 states that 'transfers through routers or other contracts, are taxed' and that only 'pool trading' is untaxed; this is false for the PoolManager contract itself. The guarantee 'Transfer rule: 1% of each ordinary transfer is burned' therefore only binds holders who do not know about the rail, and any integrator (CEX, OTC desk, bridge) can route through it to avoid the burn. The exemption itself is required by the launch floor (seed, buy and sell must clear whole), so the token cannot distinguish a swap settlement from a bridge deposit at this layer. Minimal fix: this is a design decision for the requester. Either (a) accept and document explicitly in README/NatSpec that the 1% is a burn on direct transfers only and is avoidable via PoolManager settle/take and ERC-6909 claims, or (b) if a non-avoidable 1% is required, the transfer rule itself must change (which would conflict with the floor's exact-flow requirement and is out of scope for a silent fix).","line":57,"path":"src/HIToken.sol","reproduction":"State: ALICE holds 1_000e18 HI; `poolManager` is the immutable set in the constructor. Direct path: `vm.prank(ALICE); token.transfer(BOB, 1_000e18)` -> BOB receives 990e18, totalSupply drops by 10e18. Rail path: `vm.prank(ALICE); token.transfer(poolManager, 1_000e18)` succeeds with no burn (line 57, `to == poolManager`); then the PoolManager's `take(HI, BOB, 1_000e18)` executes `HI.transfer(BOB, 1_000e18)` from the manager, again with no burn (line 57, `from == poolManager`). Result: BOB receives 1_000e18, totalSupply unchanged. On-chain this is one transaction: `PoolManager.unlock(data)` -> callback does `sync(HI); HI.transfer(PM, X); settle(); take(HI, BOB, X)`. Expected per README: 10e18 burned. Actual: 0 burned. Confirmed with a scratch test using a PoolManager stand-in whose `take` forwards from the manager address; the repository's own test_poolManagerTransfersMoveWholeBothWays (test/HIToken.t.sol:162) demonstrates the same two legs.","severity":"medium","snippet":"        if (from == poolManager || to == poolManager) return true;","title":"1% burn is bypassable by any holder through the PoolManager exemption (settle/take and ERC-6909 claims act as a fee-free transfer rail)"},{"citation":"resolved","description":"Access control / deployment-time role assignment. The supply is minted to `msg.sender` (the factory in a real launch) but the exempt 'factory' role and the address consulted for `distributorOf` come from an independent constructor argument. Nothing ties the two together. If the manifest's constructorArgs carry a static address or a stale factory address instead of `$factory`, the deployed token holds its entire supply at an address that is NOT exempt and reads the distributor from a contract that is not the launch factory. Consequences: the swarm's share and the pool seed leave the factory 1% short (or revert outright if the argument has no code), and the distributor lookup returns zero or another deployment's answer. The protected floor catches this after the fact (the swarm-share test would fail), but the token could refuse the state at construction. Suggested minimal fix that preserves the design: `if (factory_ != msg.sender) revert ...;` or derive `factory = msg.sender` and keep the argument only for manifest symmetry. The local deploy script and test_constructorMintsToWhoeverDeploys rely on deploying from a non-factory address and would need to deploy via a factory-like helper.","line":39,"path":"src/HIToken.sol","reproduction":"From EOA A: `HIToken t = new HIToken(address(otherContractWithDistributorOf), PM, 7);` -> `t.balanceOf(A) == 1e27` but `t.isExempt(A, BOB) == false`; `t.transfer(BOB, 100e18)` from A delivers 99e18 (A, the supply holder, is taxed). With a code-less argument: `HIToken o = new HIToken(address(0xFAC7), PM, 7); o.transfer(BOB, 100e18)` reverts because `distributor()` static-calls an address with no code (test_distributorLookupRevertsIfFactoryHasNoCode shows the same revert). Expected for a launch: the minter is the exempt factory. Actual: minter and factory can diverge silently at construction.","severity":"low","snippet":"    constructor(address factory_, address poolManager_, uint64 launchNumber_) ERC20(\"HI\", \"HI\") {\n        if (factory_ == address(0) || poolManager_ == address(0)) revert ZeroAddress();\n        factory = factory_;\n        poolManager = poolManager_;\n        launchNumber = launchNumber_;\n        _mint(msg.sender, TOTAL_SUPPLY);\n    }","title":"Constructor does not bind `factory` to `msg.sender`, so a wrong $factory argument yields a token whose supply holder is taxed and whose distributor lookup targets the wrong contract"},{"citation":"resolved","description":"Trust gap (access x asymmetry), reported as a documented privileged power, not a bypass. The exemption set is not fixed at deployment: the third member is read live from `ILaunchFactory(factory).distributorOf(launchNumber)` on every non-exempt transfer. Whoever controls the factory's record for this launch number controls who is fee-exempt for the life of the token. If that record is ever rewritten (upgrade, admin setter, bug), the new address becomes a fee-free sender and recipient, while all other holders keep paying 1%. The same dependency means every ordinary transfer depends on the factory having code and answering a `view` call: if the factory is self-destructed, upgraded to drop the function, or the function reverts, every non-exempt transfer reverts permanently while factory- and PoolManager-touching transfers still work. README.md lines 67-70 document the revert case but not the exemption-grant case. Also note `to == factory` (line 56) is exempt, so any post-launch sweep or forwarding function on the factory would be a second fee-free rail. Action: record in the README that the factory operator is a trusted party that can change the exempt set post-launch and can halt ordinary transfers; or, if a fixed exempt set is preferred, cache the distributor once it is non-zero (one-way latch) so a later rewrite cannot add an exempt address. The latter changes agreed behaviour (the live read is by design) and needs a scope decision.","line":58,"path":"src/HIToken.sol","reproduction":"State: ALICE holds 1_000e18; factory has recorded nothing. `factory.setDistributor(7, EVE)` (any mechanism that writes the factory's mapping). Then `vm.prank(ALICE); token.transfer(EVE, 1_000e18)` -> EVE receives 1_000e18 with zero burn; `vm.prank(EVE); token.transfer(BOB, 1_000e18)` -> BOB receives 1_000e18 with zero burn. Direct ALICE->BOB would have burned 10e18. Second case: factory code removed or `distributorOf` reverting -> `vm.prank(ALICE); token.transfer(BOB, 1)` reverts; `token.transfer(poolManager, 1)` still succeeds.","severity":"info","snippet":"        address dist = distributor();\n        return dist != address(0) && (from == dist || to == dist);","title":"Trust assumption: the factory can grant the fee exemption to any address at any time via `distributorOf`, and can brick ordinary transfers by reverting or losing code"},{"citation":"resolved","description":"Asymmetry (view vs write). The `msg.sender == factory` clause is intended for `transferFrom` called by the factory, where `msg.sender` inside `_update` is the factory. But `isExempt` is also `public view`, and when the factory (or an eth_call with `from` = factory) queries `isExempt(ALICE, BOB)` it returns true even though ALICE's own `transfer(BOB, x)` is taxed. Any factory-side or off-chain preflight that uses this view to predict whether a flow arrives whole gets the wrong answer. No funds move wrongly; the view simply does not describe the write path for other callers. Minimal fix: make the caller-dependence explicit, e.g. `isExempt(address caller, address from, address to)` with `_update` passing `_msgSender()`, or document that the view answers for the caller.","line":56,"path":"src/HIToken.sol","reproduction":"`token.isExempt(ALICE, BOB)` from an ordinary address returns false. `vm.prank(address(factory)); token.isExempt(ALICE, BOB)` returns true. `vm.prank(ALICE); token.transfer(BOB, 100e18)` delivers 99e18. Expected: a view named isExempt(from,to) describes whether a from->to transfer is exempt. Actual: the answer depends on who asks.","severity":"info","snippet":"        if (from == factory || to == factory || msg.sender == factory) return true;","title":"`isExempt` is a caller-dependent view: called by the factory it reports exempt for pairs that are taxed when they actually transfer"}],"hash":"fcffbd333b5aeffd1504d81addfbde1c3f7653f20bfa4b578641345e7858090a","nodeId":"486942a2-2122-45b7-8f95-a12a56c6284d","outcome":"completed","summary":"The review is complete. The findings file is written, the scratch test is removed, and no tracked file changed.\n\n**Outcome.** The permission model of `src/HIToken.sol` is small and mostly sound: no owner, no roles, no initializer, no supply growth path, and the factory-as-caller exemption cannot skip the allowance check. I report one medium defect, one low, and two informational trust notes, all reproduced locally with a scratch Foundry test before being written up.\n\n- **Medium, fee bypass through the PoolManager rail.** Both `to == poolManager` and `from == poolManager` are exempt, and the v4 PoolManager is permissionless. Any holder can transfer in, call `settle`, then `take` to any recipient, or mint ERC-6909 claims, moving HI peer-to-peer with zero burn. The README's claim that transfers through other contracts are taxed is false for this one. The exemption is required by the launch floor, so the fix is a disclosure or a design decision, not a one-line patch.\n- **Low, constructor does not bind the factory role to the minter.** A wrong `$factory` argument produces a token whose supply holder is taxed and whose distributor lookup hits the wrong contract. The floor would catch it after deployment, but the constructor could refuse it.\n- **Info, factory controls the exempt set post-launch.** Whoever writes `distributorOf` for this launch number can make any address fee-free, or halt all ordinary transfers by reverting. Documented as a trust assumption, with the exemption-grant case missing from the README.\n- **Info, `isExempt` is caller-dependent.** Queried by the factory it reports exempt for pairs that are taxed on an actual transfer.\n\n**Coverage.** All three listed entry points have rows, plus the constructor, the burn-branch invariants, and the privileged-surface check. Every Access Control, Trust Gap and Asymmetry item in the Pashov guide was checked against this code. Nothing in my area was left unreached. No critical or high finding exists, so no proof test is attached.","treeHash":null,"usage":{"cachedInputTokens":451374,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":16109,"runtime":"claude","turns":21,"wallClockMs":227769}}],"verification":[{"checks":[{"durationMs":1653,"exitCode":0,"name":"build","output":"Compiling 34 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.53s\nCompiler run successful!\n","passed":true},{"durationMs":11778,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/DeployHI.t.sol:DeployHITest\n[PASS] test_deployRejectsZeroFactory() (gas: 1532622)\n[PASS] test_deployWithExplicitConfigMintsToTheScriptCaller() (gas: 2216970)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 660.32µs (379.17µs CPU time)\n\nRan 27 tests for test/HIToken.t.sol:HITokenTest\n[PASS] testFuzz_ordinaryTransferConservesValue(uint256,uint256) (runs: 256, μ: 207163, ~: 210958)\nLogs:\n  Bound result 54\n  Bound result 28\n\n[PASS] test_burnRoundsDownForDustAmounts() (gas: 233401)\n[PASS] test_constructorMintsToWhoeverDeploys() (gas: 22390)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 31350)\n[PASS] test_constructorRecordsLaunchAddresses() (gas: 27054)\n[PASS] test_constructorRejectsZeroFactory() (gas: 3918)\n[PASS] test_constructorRejectsZeroPoolManager() (gas: 6067)\n[PASS] test_distributorIsReadLiveFromTheFactory() (gas: 279272)\n[PASS] test_distributorLookupRevertsIfFactoryHasNoCode() (gas: 92422)\n[PASS] test_distributorTransfersMoveWholeBothWays() (gas: 259129)\n[PASS] test_factoryAsCallerMovesWhole() (gas: 182474)\n[PASS] test_factoryCannotPullAHoldersBalanceWithoutAllowance() (gas: 95594)\n[PASS] test_factoryTransfersMoveWhole() (gas: 82180)\n[PASS] test_launchFlowsMoveExactlyWhatTheySay() (gas: 416776)\n[PASS] test_metadata() (gas: 31959)\n[PASS] test_noCallIncreasesSupplyOrMintsToACaller() (gas: 546895)\n[PASS] test_noPrivilegedCallMovesOrFreezesAHolder() (gas: 502102)\n[PASS] test_ordinaryTransferBurnsOnePercent() (gas: 159179)\n[PASS] test_ordinaryTransferFromBurnsOnePercent() (gas: 213380)\n[PASS] test_otherLaunchesDistributorIsNotExempt() (gas: 177794)\n[PASS] test_poolManagerTransfersMoveWholeBothWays() (gas: 188459)\n[PASS] test_runtimeCodeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 3454192)\n[PASS] test_selfTransferStillBurns() (gas: 126458)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 91883)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 97605)\n[PASS] test_transferToZeroAddressReverts() (gas: 89193)\n[PASS] test_transfersToFactoryMoveWhole() (gas: 113111)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 70.87ms (30.07ms CPU time)\n\nRan 19 tests for test/HITokenAdversarial.t.sol:HITokenAdversarialTest\n[PASS] testFuzz_allowanceMustCoverGrossAmount(uint256,uint256) (runs: 1000, μ: 190429, ~: 190932)\nLogs:\n  Bound result 1857124000\n  Bound result 599290588\n\n[PASS] testFuzz_delegatedSelfTransferBurnsOnceAndSpendsGrossAllowance(uint256,uint256) (runs: 1000, μ: 210813, ~: 214366)\nLogs:\n  Bound result 966102746189224615417770829\n  Bound result 1\n\n[PASS] testFuzz_delegatedTransferAccountsForGrossAmount(uint256,uint256,uint256,bool) (runs: 1000, μ: 273719, ~: 274497)\nLogs:\n  Bound result 877614323518074618745361007\n  Bound result 651692538750048798576285103\n\n[PASS] testFuzz_exemptTransferFromStillSpendsAllowance(uint256,uint8) (runs: 1000, μ: 205501, ~: 211119)\nLogs:\n  Bound result 696141476249637863037535783\n\n[PASS] testFuzz_feeIsRoundedDownWithinOneMinorUnitForTransferableAmounts(uint256) (runs: 1000, μ: 13032, ~: 12769)\nLogs:\n  Bound result 2439649222\n\n[PASS] testFuzz_overdraftIsAtomicForOrdinaryAndExemptRecipients(uint256,uint256,uint8) (runs: 1000, μ: 253816, ~: 258482)\nLogs:\n  Bound result 334334590958406432691634109\n  Bound result 1496469528567029100069734331108435407479791930314790334769255398260691857313\n\n[PASS] test_approvalDoesNotAuthorizeAnotherSpender() (gas: 187864)\n[PASS] test_approvalsReplaceRatherThanAccumulateAndCanBeRevoked() (gas: 257483)\n[PASS] test_distributorReplacementAndRemovalTakeEffectImmediately() (gas: 390576)\n[PASS] test_feeExemptSpendersStillNeedPermission() (gas: 436326)\n[PASS] test_finiteAllowanceCannotBeSpentTwice() (gas: 255895)\n[PASS] test_fullSupplyCanBeTransferredAndBurnedOnce() (gas: 172789)\n[PASS] test_infiniteAllowanceSurvivesRepeatedTaxedTransfers() (gas: 352249)\n[PASS] test_maximumTransferRevertsWithBalanceErrorBeforeFeeArithmetic() (gas: 225766)\n[PASS] test_oneWeiAndFeeThresholds() (gas: 695016)\n[PASS] test_selfTransferRequiresGrossBalanceEvenThoughOnlyFeeIsLost() (gas: 232991)\n[PASS] test_zeroRecipientRestoresAlreadySpentAllowance() (gas: 191024)\n[PASS] test_zeroTransfersFromEmptyAccountEmitAndPreserveState() (gas: 136726)\n[PASS] test_zeroValueStillRejectsZeroSenderRecipientAndSpender() (gas: 174730)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 70.99ms (381.45ms CPU time)\n\nRan 2 tests for test/HITokenInvariant.t.sol:HITokenInvariantTest\n[PASS]\nHITokenInvariantTest invariants:\n[PASS] invariant_allowancesMatchApprovalsAndSuccessfulGrossSpends\n[PASS] invariant_balancesConserveSupplyAndMatchTransferHistory\n HITokenInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭----------------+--------------------------+-------+---------+----------╮\n| Contract       | Selector                 | Calls | Reverts | Discards |\n+========================================================================+\n| HITokenHandler | approve                  | 1745  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | approveAndTransferFrom   | 1986  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | attemptOverdraft         | 1861  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | attemptZeroRecipient     | 1730  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | changeDistributor        | 1827  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | revokeAndAttemptTransfer | 1787  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | spendExistingAllowance   | 1801  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | transfer                 | 1771  | 0       | 0        |\n|----------------+--------------------------+-------+---------+----------|\n| HITokenHandler | transferFullBalance      | 1876  | 0       | 0        |\n╰----------------+--------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2062\n  Bound result 62\n  Bound result 4927\n  Bound result 0\n  Bound result 399\n  Bound result 2293517446\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 199\n  Bound result 0\n\n[PASS] test_handlerExercisesValueMovementAndRejectionPaths() (gas: 3459958)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.69s (11.69s CPU time)\n\nRan 4 test suites in 11.69s (11.83s CPU time): 50 tests passed, 0 failed, 0 skipped (50 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HIToken.approve(address,uint256)\",\"HIToken.transfer(address,uint256)\",\"HIToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":144,\"foundry.toml\":25,\"remappings.txt\":2,\"script/DeployHI.s.sol\":35,\"src/HIToken.sol\":80,\"src/interfaces/ILaunchFactory.sol\":9,\"test/DeployHI.t.sol\":26,\"test/HIToken.t.sol\":354,\"test/HITokenAdversarial.t.sol\":317,\"test/HITokenInvariant.t.sol\":86,\"test/handlers/HITokenHandler.sol\":197,\"test/mocks/MockLaunchFactory.sol\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1d9aea825047f8e2636bb84a7e8f00063b209e43e392e1398909940a1d7644ce","verifiedTreeHash":"08533542e31ecebc2c55d846ee393266b2553f45","verifierVersion":"0.1.0+e6140b7a"},{"checks":[{"durationMs":1074,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 976.50ms\nCompiler run successful!\n","passed":true},{"durationMs":96,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/DeployHI.t.sol:DeployHITest\n[PASS] test_deployRejectsZeroFactory() (gas: 1532622)\n[PASS] test_deployWithExplicitConfigMintsToTheScriptCaller() (gas: 2216970)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 806.29µs (531.41µs CPU time)\n\nRan 27 tests for test/HIToken.t.sol:HITokenTest\n[PASS] testFuzz_ordinaryTransferConservesValue(uint256,uint256) (runs: 256, μ: 208251, ~: 210970)\nLogs:\n  Bound result 2652\n  Bound result 475\n\n[PASS] test_burnRoundsDownForDustAmounts() (gas: 233401)\n[PASS] test_constructorMintsToWhoeverDeploys() (gas: 22390)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 31350)\n[PASS] test_constructorRecordsLaunchAddresses() (gas: 27054)\n[PASS] test_constructorRejectsZeroFactory() (gas: 3918)\n[PASS] test_constructorRejectsZeroPoolManager() (gas: 6067)\n[PASS] test_distributorIsReadLiveFromTheFactory() (gas: 279272)\n[PASS] test_distributorLookupRevertsIfFactoryHasNoCode() (gas: 92422)\n[PASS] test_distributorTransfersMoveWholeBothWays() (gas: 259129)\n[PASS] test_factoryAsCallerMovesWhole() (gas: 182474)\n[PASS] test_factoryCannotPullAHoldersBalanceWithoutAllowance() (gas: 95594)\n[PASS] test_factoryTransfersMoveWhole() (gas: 82180)\n[PASS] test_launchFlowsMoveExactlyWhatTheySay() (gas: 416776)\n[PASS] test_metadata() (gas: 31959)\n[PASS] test_noCallIncreasesSupplyOrMintsToACaller() (gas: 546895)\n[PASS] test_noPrivilegedCallMovesOrFreezesAHolder() (gas: 502102)\n[PASS] test_ordinaryTransferBurnsOnePercent() (gas: 159179)\n[PASS] test_ordinaryTransferFromBurnsOnePercent() (gas: 213380)\n[PASS] test_otherLaunchesDistributorIsNotExempt() (gas: 177794)\n[PASS] test_poolManagerTransfersMoveWholeBothWays() (gas: 188459)\n[PASS] test_runtimeCodeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 3454192)\n[PASS] test_selfTransferStillBurns() (gas: 126458)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 91883)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 97605)\n[PASS] test_transferToZeroAddressReverts() (gas: 89193)\n[PASS] test_transfersToFactoryMoveWhole() (gas: 113111)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 8.31ms (22.34ms CPU time)\n\nRan 2 test suites in 8.90ms (9.12ms CPU time): 29 tests passed, 0 failed, 0 skipped (29 total tests)\n","passed":true},{"durationMs":37,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HIToken.approve(address,uint256)\",\"HIToken.transfer(address,uint256)\",\"HIToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":144,\"foundry.toml\":25,\"remappings.txt\":2,\"script/DeployHI.s.sol\":35,\"src/HIToken.sol\":80,\"src/interfaces/ILaunchFactory.sol\":9,\"test/DeployHI.t.sol\":26,\"test/HIToken.t.sol\":354,\"test/mocks/MockLaunchFactory.sol\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":622,"exitCode":0,"name":"slither","output":"slither: no results at low impact or above","passed":true},{"durationMs":267,"exitCode":0,"name":"aderyn","output":"[low] large-numeric-literal at src/HIToken.sol:23: Large Numeric Literal (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"477e2100423db7487a605315259aba1ecd05b30d8a6f0e9edba531c47aa394ba","verifiedTreeHash":"0fc3c3d13804f951adb18df712ef24c6b338d733","verifierVersion":"0.1.0+5dba5e5e"},{"checks":[{"durationMs":944,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 854.38ms\nCompiler run successful!\n","passed":true},{"durationMs":83,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/DeployHI.t.sol:DeployHITest\n[PASS] test_deployRejectsZeroFactory() (gas: 1532622)\n[PASS] test_deployWithExplicitConfigMintsToTheScriptCaller() (gas: 2216970)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 392.30µs (345.39µs CPU time)\n\nRan 27 tests for test/HIToken.t.sol:HITokenTest\n[PASS] testFuzz_ordinaryTransferConservesValue(uint256,uint256) (runs: 256, μ: 208448, ~: 210970)\nLogs:\n  Bound result 783565669069947851052919029\n  Bound result 445293\n\n[PASS] test_burnRoundsDownForDustAmounts() (gas: 233401)\n[PASS] test_constructorMintsToWhoeverDeploys() (gas: 22390)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 31350)\n[PASS] test_constructorRecordsLaunchAddresses() (gas: 27054)\n[PASS] test_constructorRejectsZeroFactory() (gas: 3918)\n[PASS] test_constructorRejectsZeroPoolManager() (gas: 6067)\n[PASS] test_distributorIsReadLiveFromTheFactory() (gas: 279272)\n[PASS] test_distributorLookupRevertsIfFactoryHasNoCode() (gas: 92422)\n[PASS] test_distributorTransfersMoveWholeBothWays() (gas: 259129)\n[PASS] test_factoryAsCallerMovesWhole() (gas: 182474)\n[PASS] test_factoryCannotPullAHoldersBalanceWithoutAllowance() (gas: 95594)\n[PASS] test_factoryTransfersMoveWhole() (gas: 82180)\n[PASS] test_launchFlowsMoveExactlyWhatTheySay() (gas: 416776)\n[PASS] test_metadata() (gas: 31959)\n[PASS] test_noCallIncreasesSupplyOrMintsToACaller() (gas: 546895)\n[PASS] test_noPrivilegedCallMovesOrFreezesAHolder() (gas: 502102)\n[PASS] test_ordinaryTransferBurnsOnePercent() (gas: 159179)\n[PASS] test_ordinaryTransferFromBurnsOnePercent() (gas: 213380)\n[PASS] test_otherLaunchesDistributorIsNotExempt() (gas: 177794)\n[PASS] test_poolManagerTransfersMoveWholeBothWays() (gas: 188459)\n[PASS] test_runtimeCodeHasNoDelegatecallCallcodeOrSelfdestruct() (gas: 3454192)\n[PASS] test_selfTransferStillBurns() (gas: 126458)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 91883)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 97605)\n[PASS] test_transferToZeroAddressReverts() (gas: 89193)\n[PASS] test_transfersToFactoryMoveWhole() (gas: 113111)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 7.53ms (17.69ms CPU time)\n\nRan 2 test suites in 8.28ms (7.92ms CPU time): 29 tests passed, 0 failed, 0 skipped (29 total tests)\n","passed":true},{"durationMs":32,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HIToken.approve(address,uint256)\",\"HIToken.transfer(address,uint256)\",\"HIToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":144,\"foundry.toml\":25,\"launch.json\":20,\"remappings.txt\":2,\"script/DeployHI.s.sol\":35,\"src/HIToken.sol\":80,\"src/interfaces/ILaunchFactory.sol\":9,\"test/DeployHI.t.sol\":26,\"test/HIToken.t.sol\":354,\"test/mocks/MockLaunchFactory.sol\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b39fdcc89af61a1d1e9c1b7250e4072b9ae4d702a07dcf55163e8844bbf2a4d1","verifiedTreeHash":"d69872d5c20c7a222944b91cd42c478bd42912e9","verifierVersion":"0.1.0+5dba5e5e"}]}