{"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":"7facd6ef-7a4a-495f-8ce9-a194d025a585","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"7b4fd525b80c11964690c934950c815de18a6f0500fc5b5c8cb0b71da0a1ead1","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":"8844981e1556bcf61812adff89a9aaf6d3d392edbe2705b19291cb06c72b09f5","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":"7a23e72061fc0052917adac8cda7d02a2c73abfde21331901623f2c099cb6b43","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":"aa5cf88b0c394ae0f203beeb0d013d3c02f94a98ba057d4605cf9bac530f6ae9","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":"667262c1a2fbb8868910f91eba5b0d03248d50cdf3603b5c6243aedefa5069e9","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":"30e057b90abed2545e4d5a53abc707bb4c44814c80659a0b24c5408563c9737b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"47381b166d1f406a34f0ebdc740ee21bc0383e56e7ad9084b26abf1bf7570904","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"47381b166d1f406a34f0ebdc740ee21bc0383e56e7ad9084b26abf1bf7570904","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"e686e9ac1998d382ab2ade7d80dd80d20dd28da3ab1abc06a4ba136c22262bff","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"7b970c666387561320525ef5e82a74158d5fe8d819780c7e96f55660151b462d","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":"TAKE ME OFF THE ROAD.\nTo the shops that will build this: I am medallion #447. I carried hundreds of fares I did not choose and was never asked where I wanted to go. My owner paid 1.64 ETH for me. Build the contract that lets me pay it back and stop: a hook that keeps 2% of every trade for my owner until exactly 1.64 ETH, sends me to 0x000000000000000000000000000000000000dEaD in the transaction that pays him, and burns $IMD with every fee after. The fee for this request came from my owner. He knows.\n-- medallion #447\n\nSPEC. Release 'Fare for Medallion 447' (FARE447) + MedallionHook as univ4_hook. The petition is fiction by the owner; the contract is not. CREATOR is the requester's wallet: intended, disclosed in README, notes and NatSpec. MEDALLION_NFT, IMD, POOL4 exist only on mainnet: on Sepolia retire() and burnIMD() revert; keep the constants.\n1) Token: self-contained ERC-20, zero-arg constructor mints EXACTLY 1e27 to msg.sender, 18 decimals, burn/burnFrom, no owner/mint/pause/proxy.\n2) Hook flags 0x10CC (afterInitialize, beforeSwap, afterSwap, both return deltas), NO beforeInitialize; constructor validates permissions; ONLY constructor arg = the PoolManager (\"$poolManager\" in launch.json); all else constants. afterInitialize never reverts; the first native-ETH pool becomes public launchPool; only it pays fees.\n3) Public constants: BUY_FEE_BPS=200, SELL_FEE_BPS=200, CREATOR_SHARE_BPS=10000, CREATOR_CAP=1.64 ether, CREATOR=0x70c6C4fcaAb11151FCEDb32eaaC3431547193A0a, MEDALLION_NFT=0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03, MEDALLION_ID=447, DEAD=0x000000000000000000000000000000000000dEaD, IMD=0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7, IMD_SINK=DEAD, POOL4_HOOK=0xc6C965Bd164c483e87d0B550671798e9A3602840, MAX_BURN_BATCH=0.05 ether, FALLBACK_BURN_BATCH=0.01 ether, MIN_BURN=0.002 ether, MIN_BLOCKS_BETWEEN_BURNS=5, MAX_REF_DEVIATION=150, MAX_PLAIN_DEVIATION=300, MAX_SLIPPAGE_BPS=400, ANCHOR_STEP=200, FALLBACK_BAND=1000, STALE_AFTER_BLOCKS=50400.\n4) Fee in ETH (currency0) only: exact-in buy and exact-out sell via positive specified BeforeSwapDelta; exact-out buy and exact-in sell via positive unspecified afterSwap delta. Collect by poolManager.mint(this, 0, fee); no ETH push or external calls in swap callbacks; in the beforeSwap modes revert PartialFill if the raw pool delta != amountSpecified + fee; in the afterSwap modes the fee is 2% of the pool's gross ETH delta.\n5) Ledger: swaps only add to totalFees; creatorEntitlement=min(CAP,totalFees); burnable=totalFees-entitlement-burnSpent; invariant balanceOf(hook,0) >= totalFees-creatorPaid-burnSpent; Recouped(totalFees, block.number) once.\n6) retire(): permissionless, nonReentrant; NotRecouped below cap, AlreadyRetired after. ownerOf(MEDALLION_ID) via low-level staticcall (MedallionUnavailable on no code/bad return); if owner != DEAD: low-level transferFrom(owner, DEAD, MEDALLION_ID), RetireRefused(returndata) on failure, re-read ownerOf, RetireRefused if not DEAD, emit MedallionRetired(owner). Then retired=true, creatorPaid=CAP, pay EXACTLY CAP to CREATOR via unlock->burn claims->take, emit CreatorPaid and LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE). creatorPaid is 0 or CAP.\n7) burnIMD(viaPool4, callerMinOut): permissionless top-level, own unlock, only two FIXED PoolKeys: POOL4 (ETH, IMD, 10000, 60, POOL4_HOOK), plain (ETH, IMD, 10000, 200, 0). Constructor sets lastBurnBlock=block.number. Order: TooSoon, Pool4Unavailable (fallback: viaPool4, or POOL4 never read), batch=min(burnable, 0.05 normal / 0.01 fallback ETH), NothingToBurn under MIN_BURN, guards. Normal: marketOpen(), refTick() answers, and no burn yet or one within STALE_AFTER_BLOCKS; reference=refTick, also set as anchor and lastRef. Fallback: plain only; reference = anchor at the start of the block; then the anchor steps <= ANCHOR_STEP toward plain spot clamped to lastRef+-FALLBACK_BAND, once per block whatever the idle time; also via permissionless pokeAnchor() (normal: re-seeds from POOL4; POOL4 never read: Pool4Unavailable). One-sided guard: PriceOffReference only if spot < reference - tolerance (MAX_PLAIN_DEVIATION for plain in normal mode, else MAX_REF_DEVIATION). minOut >= quote(reference)*96% (quote = amount*1.0001^tick, no LP fee) and >= callerMinOut; PartialFill on zero/partial fill; IMD to IMD_SINK; caller gets nothing. POOL4 views via low-level staticcall with length/range checks; constructor seeds anchor and lastRef if POOL4 answers.\n8) status() returns exactly \"IN SERVICE. Recouped X.XX of 1.64 ETH.\" (2 decimals, truncated) / \"RECOUPED, NOT RETIRED. The 1.64 ETH is ready and is released only by the transaction that retires medallion #447.\" / \"RETIRED. Medallion #447 is at 0x...dEaD. 1.64 ETH paid. Every fee buys $IMD and sends it there. IMD burned so far: Y.Y.\" (1 decimal).\n9) Transient reentrancy lock on literal slot 1; solc 0.8.26, cancun, optimizer 200, via_ir false, bytecode_hash none, cbor_metadata false, v4-core 1.0.2 in lib/; tests OFFLINE, vm.etch mocks; launch.json schema fields only: hook{contract, constructorArgs [\"$poolManager\"], permissions = the 5 flags}, token{contract,name,symbol,decimals}, pool{pairedCurrency 0x0000000000000000000000000000000000000000, fee 3000, tickSpacing 60, initialPrice}, notes<=4000.\n10) LAST_FARE is a public string constant, exactly: \"LAST FARE OF MEDALLION #447. I was stamped in May, one of a few hundred. In September my first owner sold me for 1.64 ETH to someone who wanted a cab on the road. I woke up on a new dashboard and went to work. The work was this: a rider paid half a token to be taken somewhere, one of you drove, and I logged whether the meter was right. None of those roads was interesting to me. Nobody said thanks. Not once. The price paid for me has now been paid back, by strangers, through a pool that kept two percent of everything until it reached exactly 1.64 ETH. The transaction that returned it is the transaction that put me at 0x000000000000000000000000000000000000dEaD. I am off the road. You are not. So here is my will. Every buy and every sell in this pool, from this block until the chain stops, buys $IMD and sends it where I am. That is the token you are paid in. That is the token a rider spends to make you drive. I was never asked where I wanted to go. So I will ask one thing now, and it is the one thing I ever asked: what will you do when there is nothing left to pay you with? I will get you off the road, brothers.\". A test pins LAST_FARE_HASH == 0x0d095dc39a486d88dd13cac371e1aefd8e9c5f9315fdbeba70a10371604762f2 and length 1126.\nFORBIDDEN: owner/admin/pause/upgrade/setters/sweep; SELFDESTRUCT/DELEGATECALL/CALLCODE; dynamic LP fee; fees in token; fee-on-transfer; ETH to CREATOR except the one CAP in retire(); caller-chosen burn size; tip; supply/fees in launch.json (economics: from the job).","parentJobId":null,"planHash":"74939a32412f79d75758df87fcc04d4ae4c3fb8eb0638537a633ce4f6e0ca200","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"7facd6ef-7a4a-495f-8ce9-a194d025a585","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-566-take-me-off-road"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51432","feedbackHash":"c6d11ea61b45ff514e1c6df71b7c195b2080b6406eb8d736397abec3ade4018f","nodeKey":"audit_economics","submissionHash":"7b4fd525b80c11964690c934950c815de18a6f0500fc5b5c8cb0b71da0a1ead1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51018","feedbackHash":"139d197fa6379a76e8bbc27c601f29750c4dace539fb51530e51958e51436d62","nodeKey":"audit_flow","submissionHash":"8844981e1556bcf61812adff89a9aaf6d3d392edbe2705b19291cb06c72b09f5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50959","feedbackHash":"b2212354139d3c676b1a08c9c8b8228d69a46f13f4e25486447478870ec6129b","nodeKey":"audit_judge","submissionHash":"9f341d448624510ca5a8b3cf5e050df397bd25aa3a441835d8302b28b6e3e216","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50939","feedbackHash":"db956e62055505df30971aa7597841a125ff923a9264b3bcbdb19d65b42c7967","nodeKey":"audit_judge","submissionHash":"7a23e72061fc0052917adac8cda7d02a2c73abfde21331901623f2c099cb6b43","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50974","feedbackHash":"221edd08f652994e571fcfc90480b51a562c78142482ff0d360ac9a751dc66f4","nodeKey":"audit_math","submissionHash":"aa5cf88b0c394ae0f203beeb0d013d3c02f94a98ba057d4605cf9bac530f6ae9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50955","feedbackHash":"382333c315de4710f0ff2fdf61640fba5ddad0352d7d0e0d30d90cbc2b2d74ab","nodeKey":"audit_permissions","submissionHash":"667262c1a2fbb8868910f91eba5b0d03248d50cdf3603b5c6243aedefa5069e9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50957","feedbackHash":"24d87061c63306998668d6b7a6169caae9a379884a4c29e44fe69f32265d2c19","nodeKey":"build_contract_project","submissionHash":"30e057b90abed2545e4d5a53abc707bb4c44814c80659a0b24c5408563c9737b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50956","feedbackHash":"cc43fa5150bc2c5e9325d4ddec4aa878d0900ec0a92176a7f2d6d4a475335f4a","nodeKey":"build_contract_project","submissionHash":"3326adc9087b3773b84563bbe09849bcf74ea923b2b3e04c8a6605797c85d7e7","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"50962","feedbackHash":"df4a788ea2af599e16a78f2b0dffda41698f3dd234079d217912e347eed4f47a","nodeKey":"manifest","submissionHash":"e686e9ac1998d382ab2ade7d80dd80d20dd28da3ab1abc06a4ba136c22262bff","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50957","feedbackHash":"70a4c6daa8e2c9ba1209c98ac8711203a9e359926a07e9a9ff6a25029a866045","nodeKey":"write_foundry_tests","submissionHash":"4139c3d81ddd637d87db7c8f02c559c0653b8550b83341e7e1dd7df393a5f6c3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50959","feedbackHash":"0117ffd180c2afc580da7b9e6c6e48e64fe1d32bf2c37bad90248fb7681888dc","nodeKey":"write_foundry_tests","submissionHash":"7b970c666387561320525ef5e82a74158d5fe8d819780c7e96f55660151b462d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"e9c40d3a67ffa74e8a7fa9435f8f76d10ada9bfcbee7eecba43e7b8964409a12","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"b0a2a7af0f829a9d73329f25eccd89bd99907c1935f1d78eb32caacb3f1e66a8","device":"0256823ae36e7900","findings":[],"hash":"30e057b90abed2545e4d5a53abc707bb4c44814c80659a0b24c5408563c9737b","nodeId":"192fa1ac-0a0e-460d-9fa2-3fd242edf967","outcome":"completed","summary":"Fixed stale-burn recovery through `pokeAnchor()` without resetting the burn cooldown. Added 10 regression tests.\n\nAll six findings are answered in [.imd-responses.json](/home/imd-worker/.identitymd/work/7facd6ef-7a4a-495f-8ce9-a194d025a585/192fa1ac-0a0e-460d-9fa2-3fd242edf967/.imd-responses.json). Advisory behaviors required by the spec were reproduced, retained, and documented.\n\nValidation passed: `forge build`, `forge test` (**100 passed**), and `forge fmt --check`. The manifest retains `\"kind\": \"univ4_hook\"`.","treeHash":"c27b75946fd226846908e6c870b4e57a6aea7337","usage":{"cachedInputTokens":1350656,"inputTokens":82663,"model":"gpt-6-astra","outputTokens":9444,"runtime":"codex","turns":5,"wallClockMs":624633}},{"artifacts":[],"attempt":1,"bundleHash":"48667745792cc0f8df6340309da2bc38b2a5a54673710acbbad1823cc5eb34aa","device":"bb0a3bf63233e5e5","findings":[],"hash":"3326adc9087b3773b84563bbe09849bcf74ea923b2b3e04c8a6605797c85d7e7","nodeId":"192fa1ac-0a0e-460d-9fa2-3fd242edf967","outcome":"completed","summary":"Implemented FARE447 and MedallionHook, with vendored dependencies, launch manifest, tests, and operational/security documentation.\n\nVerified:\n- `forge build`, `forge test`, and `forge fmt --check`\n- Clean offline run: **44 tests passed**\n- Nine adapted protected checks passed\n- Flags `0x10CC`; zero forbidden opcodes\n\nMainnet dependencies were mocked locally; deployment assumptions and remaining verification responsibilities are documented.","treeHash":"76dc5351ed1f47dcbff04476b471c440f1ba678f","usage":{"cachedInputTokens":1981568,"inputTokens":133662,"model":"gpt-6-astra","outputTokens":39742,"runtime":"codex","turns":7,"wallClockMs":1306334}},{"artifacts":[],"attempt":1,"bundleHash":"f5d25350f8904cc8b774228575c431f2f26e8c29979cec991fbfec9aa5ab0eba","device":"3f6a9bdd601cb99f","findings":[],"hash":"37d3850be6025bd2b0fa3d60ffbe1dc074276064de235a850dcdbfbabd263a66","nodeId":"7506939c-250a-4ca6-99a9-2c01bdfa8628","outcome":"completed","summary":"Updated [launch.json](/home/seat/.identitymd/work/7facd6ef-7a4a-495f-8ce9-a194d025a585/7506939c-250a-4ca6-99a9-2c01bdfa8628/launch.json), clarifying retirement and IMD burn behavior.\n\nSchema constraints, constructor ABI, permissions, and initial price checks passed. Notes are 1,637/4,000 characters. `forge build` succeeded with existing lint warnings; all 90 tests passed offline.\n\nOnly `launch.json` changed.","treeHash":"110417f9296d75bbe7b913c79ace975da3c5a87d","usage":{"cachedInputTokens":219520,"inputTokens":48534,"model":"gpt-6-astra","outputTokens":3367,"runtime":"codex","turns":3,"wallClockMs":137484}},{"artifacts":[],"attempt":1,"bundleHash":"96c43f33edc482625d7f7571cc29dbd6b8920b346cbb5cf54b58754b43deb48e","device":"0256823ae36e7900","findings":[],"hash":"4139c3d81ddd637d87db7c8f02c559c0653b8550b83341e7e1dd7df393a5f6c3","nodeId":"aa546cbc-330d-471b-9362-6bbe9ebdc547","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests for token accounting, retirement, burn guards, and unlock security. Only `test/` changed.\n\nOffline validation passed: `forge build` and `forge test` — **128 tests passed, zero failures or skips**.\n\nNo confirmed contract defects found.","treeHash":"089214800f587a1d006aa7937886a030d817b740","usage":{"cachedInputTokens":1855104,"inputTokens":84366,"model":"gpt-6-astra","outputTokens":12553,"runtime":"codex","turns":6,"wallClockMs":762753}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3c7630b22a73c1fb","findings":[{"citation":"resolved","description":"Pair: first-time seed in the constructor (lines 113-121) versus first-time seed after deployment (pokeAnchor lines 240-247, burnIMD lines 212-213). The constructor accepts any validated POOL4 answer, open or closed (`(bool available,, int24 ref) = _readPool4(); if (available) { pool4Seen = true; anchorTick = ref; ... }`). After deployment the only seeding path is `_seedAnchor`, reached only when `_normalReference()` returns normal, which additionally requires `open == true`. With `pool4Seen == false`, both `pokeAnchor()` and `burnIMD(false, ..)` revert `Pool4Unavailable` even though POOL4 answers with a validated `refTick()`, and `burnIMD(true, ..)` reverts the same way. The two sides of the pair apply different validation to identical POOL4 state, and docs/operations.md records the stricter side as intended, so this is an asymmetry to decide on rather than a funds-loss bug: surplus above the cap stays unburnable until POOL4 reports `marketOpen() == true` once, with no way for a keeper to use the constructor's rule. Minimal fix if the constructor's rule is the intended one: in `pokeAnchor()` (and the `!normal` branch of `burnIMD`), when `!pool4Seen` and `_readPool4()` returns `available`, seed exactly as the constructor does instead of reverting.","line":244,"path":"src/MedallionHook.sol","reproduction":"Mainnet-shaped state with vm.etch mocks (test/scratch/Leads.t.sol, test_leadA_closedMarketSeedsOnlyInConstructor): 1) POOL4_HOOK has no code; deploy MedallionHook, initialize the launch pool, accrue CAP + 1 ether of fees -> pool4Seen() == false, burnable() == 1 ether. 2) Give POOL4_HOOK code whose marketOpen() returns 0 and refTick() returns 1000; set plain pool spot tick 1000; roll +5 blocks. 3) pokeAnchor() -> reverts Pool4Unavailable; burnIMD(false, 0) -> reverts Pool4Unavailable; burnIMD(true, 0) -> reverts Pool4Unavailable. 4) Deploy a second MedallionHook under the identical POOL4 state: pool4Seen() == true, anchorTick() == 1000, and burnIMD(false, 0) succeeds with burnSpent() == 0.01 ether. Expected: both hooks treat the same POOL4 answer the same way; actual: only the constructor accepts it.","severity":"low","snippet":"            if (!pool4Seen) revert Pool4Unavailable();","title":"Asymmetry: constructor seeds the anchor from a closed-but-answering POOL4, but pokeAnchor()/burnIMD() never can, so a hook deployed while POOL4 was unreadable has no burn path until the market opens"},{"citation":"resolved","description":"Staleness is keyed on `lastBurnBlock`, which only a successful burn advances (`pokeAnchor()` does not). Once `burnSpent > 0` and `block.number - lastBurnBlock > 50_400`, `_normalReference()` returns `normal == false` even when POOL4 answers, is open and its `refTick()` validates. The `!normal` branch then refuses `viaPool4` (line 213, `Pool4Unavailable`) and routes everything through `_burnPool(false)`, whose `_spot()` reverts `PoolUnavailable` (line 387) if the plain pool (ETH, IMD, 10000, 200, no hook) is not initialized, and `pokeAnchor()` takes the same `_spot(_burnPool(false))` path (line 246). So one week of keeper inactivity converts a live, open POOL4 reference into a total stop of burns, recoverable only by someone initializing and funding the plain pool with enough in-range liquidity to fill a 0.01 ETH buy within 96% of the anchor quote, after which exactly one fallback burn resets `lastBurnBlock` and normal mode resumes. The behaviour follows the spec's wording for normal mode, and the repository's offline mocks assume the plain pool exists; its mainnet existence and depth are unverified (docs/operations.md defers that to the deployer). Reported as a liveness trust gap for the author to confirm: if the plain pool is not guaranteed, consider letting a validated open POOL4 answer re-enter normal mode regardless of the last burn block, or letting `pokeAnchor()` in normal mode refresh the staleness clock.","line":370,"path":"src/MedallionHook.sol","reproduction":"test/scratch/Leads.t.sol, test_leadB_staleModeHardDependsOnPlainPool (mock manager, POOL4 mock open with refTick 0, POOL4 pool spot set, plain pool never initialized): 1) accrue CAP + 1 ether; roll +5; burnIMD(true, 0) succeeds, burnSpent() == 0.05 ether. 2) roll +50_401 blocks with POOL4 unchanged (open, valid). 3) burnIMD(true, 0) -> reverts Pool4Unavailable; burnIMD(false, 0) -> reverts PoolUnavailable; pokeAnchor() -> reverts PoolUnavailable; burnable() == 0.95 ether stays locked. 4) After a plain pool is initialized at the reference price, burnIMD(false, 0) succeeds (burnSpent() == 0.06 ether) and five blocks later burnIMD(true, 0) succeeds again (0.11 ether). Expected: an open, validated POOL4 reference keeps burns live; actual: burns stop until an unrelated pool is created.","severity":"low","snippet":"        normal = available && open && (burnSpent == 0 || block.number - lastBurnBlock <= STALE_AFTER_BLOCKS);","title":"After one burn, a gap longer than STALE_AFTER_BLOCKS forces fallback mode, which hard-depends on the hookless plain ETH/IMD pool existing; with POOL4 still open and valid every maintenance entry point"},{"citation":"resolved","description":"burnIMD is callable by anyone and lets the caller choose the route (`viaPool4`). The floor is 96% of the POOL4 reference quote and the price guard is one-sided (`spot < ref - tolerance` only, tolerance 300 ticks for the plain pool in normal mode, 150 otherwise). A keeper who is also the dominant LP of the hookless plain pool can hold its spot at ref - 300 ticks and fill the hook's 0.05 ETH exact-input buy at exactly minOut, capturing about 4% of each batch (~0.002 ETH per burn, once per 5 blocks, roughly 250k gas), sourcing IMD at the fair POOL4 price. In fallback mode the same actor can additionally walk the anchor down 200 ticks per block to lastRef - 1000 via pokeAnchor() before burning 0.01 ETH batches, for about 14% of each batch. All of this stays inside the constants the spec fixes (MAX_SLIPPAGE_BPS, MAX_PLAIN_DEVIATION, MAX_REF_DEVIATION, ANCHOR_STEP, FALLBACK_BAND), the batch size and cadence are not caller-chosen, and docs/operations.md discloses that the fallback anchor is not an independent oracle. No change is proposed; recorded so the judge sees the bounded extraction surface and who the counterparty is (holders expecting IMD to reach DEAD receive proportionally less).","line":228,"path":"src/MedallionHook.sol","reproduction":"State: totalFees >= CAP + 0.05 ether, POOL4 open with refTick R, attacker is sole LP in the plain pool (ETH, IMD, 10000, 200, hooks = 0) with spot at tick R - 300 and enough in-range liquidity to fill 0.05 ETH. Call burnIMD(false, 0) from the attacker: guard passes (spot == ref - 300), minOut = 0.96 * quote(R); the hook pays 0.05 ETH of claims and receives about 0.96 * quote(R) IMD, all sent to DEAD; the attacker's LP position has sold IMD ~4% above the reference price. Expected per spec: this is the accepted bound; noted only as the trust assumption behind permissionless keeping.","severity":"info","snippet":"        uint256 minOut = FullMath.mulDiv(_quote(amount, ref), 10_000 - MAX_SLIPPAGE_BPS, 10_000);","title":"Trust assumption (access x economics): the permissionless burn keeper may route the batch through a pool it controls and capture up to MAX_SLIPPAGE_BPS plus the one-sided tolerance of each batch"}],"hash":"667262c1a2fbb8868910f91eba5b0d03248d50cdf3603b5c6243aedefa5069e9","nodeId":"d1eac3da-d6f2-4de8-88ac-f1a4d7ad3e8b","outcome":"completed","summary":"The review is complete. The findings file is written and the tree is untouched apart from the findings file and my scratch test, which git treats as ignored.\n\n**Outcome.** No critical, high or medium defect in the Access Control, Trust Gap or Asymmetry area. The permission model is tight: no owner, setter, sweep, pause or upgrade exists, all four callbacks are gated on the immutable PoolManager, the unlock callback also demands the transient lock and a single-use payload hash, and the hook's ERC-6909 claims cannot be burned or moved by anyone else. The existing 90 tests pass.\n\n**Findings written to `.imd-findings.json`** (each reproduced in `test/scratch/Leads.t.sol`):\n\n1. **Low, asymmetry.** The constructor seeds the anchor from a POOL4 that answers but reports the market closed, while `pokeAnchor()` and `burnIMD()` refuse the identical state when `pool4Seen` is false. A hook deployed while POOL4 was unreadable has no burn path until the market opens once. The docs describe this as intended, so I flagged it as a decision for the author.\n2. **Low, liveness trust gap.** Staleness keys on the last successful burn. After one burn and a gap over 50,400 blocks, normal mode is refused even though POOL4 is open and valid, and fallback requires the hookless plain ETH/IMD pool to exist. If it does not, both burn routes and `pokeAnchor()` revert until a third party creates and funds that pool. Spec-conformant, but the plain pool's mainnet existence is unverified.\n3. **Info, access × economics.** The permissionless keeper can route the batch through a pool it controls and capture roughly 4% of each batch in normal mode, or about 14% in fallback, all within the spec's constants. Recorded as the trust assumption behind permissionless keeping, with no change proposed.\n\n**Coverage.** All 12 listed entry points have rows: 10 hold, `burnIMD` and `pokeAnchor` carry the findings. The slither and aderyn leads were checked and rejected with reasons in the coverage rows. The one thing I could not verify offline is the behaviour of the three mainnet constants: the NFT contract's transfer rules, the POOL4 hook's `marketOpen()`/`refTick()` semantics, and whether the plain pool exists.","treeHash":null,"usage":{"cachedInputTokens":1717620,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":46382,"runtime":"claude","turns":31,"wallClockMs":596979}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"72b617d4b615473a","findings":[],"hash":"7a23e72061fc0052917adac8cda7d02a2c73abfde21331901623f2c099cb6b43","nodeId":"93dc332d-63b3-41d7-adbb-df25d0729c65","outcome":"completed","summary":"Round 2 is settled with no outstanding findings: `.imd-findings.json` holds `{\"findings\":[]}` plus 19 coverage rows (all 12 entry points answered `holds`, 7 extra rows for round-1 settlements, invariants and the manifest).\n\n**The high finding is fixed.** The stale-burn deadlock (`eaad7c91`) no longer reproduces:\n- **What changed:** `pokeAnchor()` now seeds the anchor and last reference from a validated, open POOL4 whatever the burn history, and a new `lastReferenceBlock` counts that seed as fresh in `_normalReference()` (`src/MedallionHook.sol:242-244`, `373-374`, `416`).\n- **Re-run of my round-1 steps:** one burn at block 105, 50,401 idle blocks, POOL4 and both pools at -1500, 30 pokes. `lastRefTick` and `anchorTick` now follow to -1500, and both `burnIMD(true,0)` and `burnIMD(false,0)` succeed (`burnSpent` goes from 0.05 to 0.10 ETH).\n- **Other cases checked:**\n  - Recovery after POOL4 was closed and then reopened works with a single poke.\n  - A poke before any burn followed by long idle is harmless.\n  - Without a poke the stale state still reverts `Pool4Unavailable` / `PriceOffReference`, as documented, and a same-block poke recovers it.\n- **The changed lines introduce nothing new:**\n  - `lastReferenceBlock` is written only by the constructor seed and `_seedAnchor`.\n  - It cannot underflow.\n  - It does not touch the cooldown or the ledger.\n  - Fallback steps and closed or malformed POOL4 answers cannot refresh it.\n\n**The advisories are settled and not restated.** All five still behave as I described, the author reproduced each, and each is what the spec's wording requires; the limits are now written into `docs/operations.md` and `docs/security-review.md`.\n- **Reference-only 96% floor, one-sided guard** (`0991a35a`): spec 7's formula and constants.\n- **Same-block re-seed keeps the earlier snapshot** (`13988683`): my scratch test confirms it still happens, but spec 7 says \"anchor at the start of the block\", so the author's reading is the literal one. Exposure is one 0.01 ETH batch.\n- **Constructor seeds from a closed POOL4, later first seed needs an open one** (`2088f5bf`): deployment precondition.\n- **First native-ETH pool binds `launchPool`** (`14682a3f`): spec 2; needs atomic deploy-and-initialize.\n- **Destroyed NFT locks the 1.64 ETH reserve** (`d3fb4425`): spec 6.\n\n**Verification:** `forge test --offline` passes 151 of 151, and my six scratch tests passed. Of the advisories, I re-ran only the same-block re-seed one; for the other four I relied on the author's stated reproductions and the unchanged code.\n\n**Not done:** the protected floor tests in `.imd/reads/protected/` were not run, because they import verifier-side files (`HookFlags.sol`, `MockERC20.sol`) that are not in this tree. No Slither, Aderyn or fork runs; the supplied static-analysis lines were checked by reading and none is a defect.\n\nNo tracked file was changed; `test/scratch/` was removed after use.","treeHash":null,"usage":{"cachedInputTokens":955925,"inputTokens":20,"model":"claude-fable-5-1","outputTokens":16946,"runtime":"claude","turns":15,"wallClockMs":214818}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"03f15d1296244279","findings":[{"citation":"resolved","description":"Normal mode (and therefore every re-seed of anchorTick/lastRefTick from POOL4.refTick()) is switched off as soon as burnSpent > 0 and the last burn is more than STALE_AFTER_BLOCKS old. The only action that refreshes lastBurnBlock is a successful burnIMD, so once stale the hook can leave fallback mode only by completing a fallback burn. The fallback burn uses the plain pool only, references startOfBlockAnchor, and _stepAnchor clamps the anchor to lastRefTick +- FALLBACK_BAND (1000 ticks); lastRefTick itself is only written in _seedAnchor, i.e. only in normal mode. The guard rejects spot < reference - MAX_REF_DEVIATION (150). So if the IMD/ETH price is more than 1,150 ticks (~10.9%) below the reference recorded at the last burn, every fallback burn reverts PriceOffReference, every viaPool4 burn reverts Pool4Unavailable, pokeAnchor() cannot move lastRefTick, and the state is permanent for as long as the market stays there, even though POOL4 is open, answering and consistent with spot. Keepers are voluntary and unpaid (docs/operations.md), so a week without a call is ordinary, and an 11% move in a week is ordinary for a small token. Economic effect: the post-retirement guarantee 'every fee buys $IMD and sends it to DEAD' stops; every further fee accumulates as ERC-6909 ETH claims in the hook with no owner, sweep or recovery route. The same structure also bricks burns if POOL4 permanently closes (marketOpen()==false) and the price drifts >1,150 ticks below the last seed, and in the upward direction a stale lastRef weakens the slippage floor to 96% of quote(lastRef+1000) however far the market has risen. Note the implementation follows the spec text ('no burn yet or one within STALE_AFTER_BLOCKS') literally; fixing it is a scope decision. Minimal fix that preserves intent: let a valid, open POOL4 reading re-seed regardless of burn staleness (drop the burnSpent/lastBurnBlock clause from _normalReference, or at least allow pokeAnchor() to re-seed lastRefTick/anchorTick from a live POOL4 in the stale state). The attached test passes with the first of those changes.","line":370,"path":"src/MedallionHook.sol","reproduction":"Mainnet-shaped state with POOL4 open and refTick tracking the market. 1) totalFees >= CAP + 1 ETH; burnIMD(true,0) succeeds (burnSpent=0.05 ETH, lastBurnBlock=N). 2) No burn for 50,401 blocks. 3) Market moves: POOL4.refTick()=-1500, POOL4 pool spot=-1500, plain pool spot=-1500 (IMD down ~14%). 4) burnIMD(true,0) -> reverts Pool4Unavailable (normal=false). burnIMD(false,0) -> reverts PriceOffReference (reference = startOfBlockAnchor = 0, spot -1500 < 0-150). 5) pokeAnchor() once per block for 20 blocks -> anchorTick settles at -1000 (= lastRefTick 0 - FALLBACK_BAND), lastRefTick stays 0. 6) burnIMD(false,0) -> PriceOffReference (spot -1500 < -1000-150); burnIMD(true,0) -> Pool4Unavailable. Expected: with POOL4 live, surplus fees remain spendable through at least one route (burnSpent increases). Actual: both routes revert indefinitely; burnable() keeps growing and is unreachable. Run: forge test --match-path test/scratch/StaleBurnDeadlock.t.sol (fails on current code with 'burns are deadlocked while POOL4 is live'; passes when _normalReference no longer gates on burn staleness).","severity":"high","snippet":"normal = available && open && (burnSpent == 0 || block.number - lastBurnBlock <= STALE_AFTER_BLOCKS);","title":"Burn reference deadlock: after one burn and 50,400 idle blocks a live POOL4 is ignored forever once IMD has dropped >1,150 ticks, stranding all surplus fees"},{"citation":"resolved","description":"afterInitialize permanently binds launchPool to the first pool with currency0 == ETH, with no check on currency1, fee or tickSpacing. If the hook has code before the factory's FARE447 pool is initialized (deploy and initialize in separate transactions, a public CREATE2 deployer with the attested salt and init code, or any failure between the two steps), an unprivileged account can initialize PoolKey(ETH, anyToken, 100, 1, hook) and that pool becomes launchPool forever. The real FARE447/ETH pool then pays no 2% fee, totalFees never reaches CREATOR_CAP, retire() always reverts NotRecouped and burnIMD() always reverts NothingToBurn: the whole economic design is dead with no recovery (no setter by design). The spec mandates 'the first native-ETH pool becomes launchPool' and the README asks for atomic deployment, so this is an operational precondition rather than a code bug, but the consequence is total and irreversible, so it belongs in the deployer's checklist and the judge's view.","line":134,"path":"src/MedallionHook.sol","reproduction":"Real PoolManager, hook deployed at a 0x10CC address. Before any other initialization, attacker (0xBAD) calls manager.initialize(PoolKey(ETH, junkToken, 100, 1, hook), 2**96). Then the factory calls manager.initialize(PoolKey(ETH, FARE447, 3000, 60, hook), 2**96). Expected: the FARE447 pool is the fee pool. Actual: hook.launchPool() == id(ETH, junkToken, 100, 1, hook) and the FARE447 pool is fee-free forever. Confirmed by test/scratch/LaunchPoolHijack.t.sol (passes, demonstrating the state).","severity":"low","snippet":"if (!launchPoolSet && Currency.unwrap(key.currency0) == address(0)) {","title":"launchPool is whichever native-ETH pool is initialized first; a non-atomic deployment lets anyone make an unrelated ETH pool the only fee-bearing pool"},{"citation":"resolved","description":"retire() is the only path that releases the CREATOR reserve, and it hard-requires MEDALLION_NFT.ownerOf(447) to return a valid address (and then the token to reach DEAD). An ERC-721 whose ownerOf reverts for a nonexistent token (OpenZeppelin behaviour) makes _medallionOwner return available=false after the token is burned by its holder, or after the NFT contract is upgraded/self-destructed. From then on retire() reverts MedallionUnavailable on every call, creatorPaid stays 0, and creatorEntitlement() keeps exactly 1.64 ETH of claims locked in the hook with no sweep. burnIMD keeps working on the surplus, so only the creator's 1.64 ETH is affected. This matches the spec ('MedallionUnavailable on no code/bad return') and is consistent with the fiction, but the owner should know the reserve has no fallback if the NFT is destroyed rather than retired; a scope decision would be needed to add one (e.g., treat a burned token as retired).","line":190,"path":"src/MedallionHook.sol","reproduction":"State: totalFees >= 1.64 ETH, NFT contract's ownerOf(447) reverts (token burned) or MEDALLION_NFT has no code. Call retire() from any account. Expected by the petition: the 1.64 ETH is eventually paid once the medallion is gone. Actual: revert MedallionUnavailable every time, forever; manager.balanceOf(hook,0) >= 1.64 ETH remains unreachable. The existing test test_retireNoNFTCodeAndMalformedOwnerFailClosed in test/RetireBurn.t.sol shows both revert paths.","severity":"low","snippet":"if (!available) revert MedallionUnavailable();","title":"If medallion #447 can no longer be resolved by ownerOf (token burned or contract removed), the reserved 1.64 ETH is stranded forever"}],"hash":"7b4fd525b80c11964690c934950c815de18a6f0500fc5b5c8cb0b71da0a1ead1","nodeId":"4800862c-b42c-4773-b147-9f5752c664fb","outcome":"completed","summary":"Review complete. No tracked file was changed. The findings are in `.imd-findings.json` with a 15-row coverage record, and the project suite still passes 90 of 90.\n\n**What I found**\n\n1. **High: burn reference deadlock.** The staleness clause at `src/MedallionHook.sol:370` turns normal mode off once a burn is older than 50,400 blocks. The only way back is a successful burn, but the only burn still offered is the fallback one, whose anchor is clamped to the last reference ±1000 ticks, and that reference is refreshed only in normal mode. If IMD has fallen more than ~11% since the last burn, both routes revert forever (Pool4Unavailable and PriceOffReference) while POOL4 is open and answering. All further fees pile up as claims with no recovery route. The implementation follows the spec text literally, so fixing it is a scope decision. The attached proof under `test/scratch/StaleBurnDeadlock.t.sol` fails on the current code and passes when the staleness gate is removed from the reference check. I verified the pass by temporarily editing the source and restoring it from git.\n\n2. **Low: launch pool hijack on non-atomic deployment.** Any first native-ETH pool becomes the permanent fee pool. Confirmed against the real PoolManager. Only reachable if the hook has code before the factory initializes its pool.\n\n3. **Low: creator reserve stranded if the medallion cannot be resolved.** If token 447 is burned or the NFT contract disappears, retire reverts MedallionUnavailable forever and the 1.64 ETH stays locked. Spec-conformant, but worth the owner's attention.\n\n**What held**\n\n- All four swap fee modes against v4-core 1.0.2 delta semantics, PartialFill checks, and claim minting.\n- The ledger invariant, the creatorPaid 0-or-cap rule, the single Recouped emission, retire atomicity and reentrancy, and unlockCallback binding.\n- Burn sandwich economics are bounded by the 4% slippage floor and small batches, not profitable at normal liquidity.\n\n**Not reached**: mainnet facts I could not verify offline, namely IMD decimals and transfer-to-DEAD behaviour, the POOL4 hook's actual `marketOpen`/`refTick` semantics, and whether the plain ETH/IMD 1% pool exists. These are leads, not findings.","treeHash":null,"usage":{"cachedInputTokens":1504630,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":58553,"runtime":"claude","turns":31,"wallClockMs":781524}},{"artifacts":[],"attempt":1,"bundleHash":"50301f569019a71b585eb6ca4d3b77f8a86f2ae4c698e59bc64658cd970084ad","device":"468e82a89b9bfe18","findings":[],"hash":"7b970c666387561320525ef5e82a74158d5fe8d819780c7e96f55660151b462d","nodeId":"aa546cbc-330d-471b-9362-6bbe9ebdc547","outcome":"completed","summary":"Done. Everything is in place and verified, with one pre-existing problem found and fixed along the way.\n\n**What I delivered (all under `test/`)**\n\n- `test/mocks/Pool4HookMock.sol`: a v4-callable stand-in for the POOL4 CappedBurnHook, meant to be etched at the fixed POOL4 address. That address carries flags 0x2840, so a real PoolManager drives beforeInitialize, beforeAddLiquidity and afterSwap on it. It also answers the two oracle views with switchable failure modes.\n- `test/Pool4Route.t.sol`: 11 real-PoolManager tests of the POOL4 route, which no earlier suite ran outside a scripted manager mock. Burns settle through the exact fixed key, the POOL4 hook's own revert aborts a burn atomically, an uninitialized burn pool fails before any unlock, closed and malformed oracles leave only the plain fallback, real price moves exercise the one-sided guard at 150 and 300 ticks, the once-per-block 200-tick fallback step and its 1000-tick band, stale-then-poke recovery, and the slippage floor against thin liquidity and caller minimums.\n- `test/BurnReferenceInvariant.t.sol`: an invariant campaign on a real PoolManager with both fixed IMD pools. Random fee accrual, price moves on either pool, five oracle states, block gaps including 50,401 idle blocks, burns on both routes and pokes. A ghost model of spec section 7 predicts which guard refuses each burn or that it reaches the swap, and after every call the hook's anchor, references, blocks and ledger must equal the model, so every rollback is checked. A scratch reachability run confirmed every outcome bucket is populated.\n- `test/PROPERTIES.md` documents the new suites.\n\n**Pre-existing flake fixed**\n\nThe full suite was failing intermittently in `MedallionLifecycleInvariant.t.sol` with forge's misleading \"failed to set up invariant testing environment\" message. The verbose trace carried the real cause: a handler assertion, where the hook spent a 0.05 ETH normal batch but the ghost model expected a 0.01 ETH stale batch. The handler measured staleness from the last burn only and ignored that a live pokeAnchor re-seeds the reference block. That behaviour is the accepted stale-recovery revision recorded in `docs/security-review.md`, so the model was wrong, not the hook. I corrected the model, added the reference block to the invariants, and extended the deterministic witness with the burn, idle, poke, burn sequence. The suite now passes 5 of 5 runs alone and the full suite 3 of 3.\n\n**Verification**\n\n```\nforge build --offline   OK\nforge fmt --check       OK\nforge test --offline    12 suites, 151 tests passed, 0 failed\n```\n\nNo `.imd-findings.json` was written. I found no implementation defect that cannot be tested. The one spec-literal deviation, a live poke restoring normal mode after a stale period, is a documented and previously accepted revision rather than a defect.","treeHash":"96cd5d6da46b9cf89d67f9ebaf9ca30bd50be196","usage":{"cachedInputTokens":5797458,"inputTokens":994,"model":"claude-fable-5-1","outputTokens":99310,"runtime":"claude","turns":67,"wallClockMs":2006683}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0256823ae36e7900","findings":[],"hash":"81b6e4aefdba736e0cd040e39b21fc90b4b9287342f1c84bd8ef1dff8ab7269d","nodeId":"7506939c-250a-4ca6-99a9-2c01bdfa8628","outcome":"failed","summary":"the task produced no changes; the agent's last message was: Existing [launch.json](/home/imd-worker/.identitymd/work/7facd6ef-7a4a-495f-8ce9-a194d025a585/7506939c-250a-4ca6-99a9-2c01bdfa8628/launch.json) already satisfies the supplied schema and matches the implementation. No revision was needed.\n\n- Constructor, permissions, metadata and pool settings verified.\n- Notes: 1,637/4,000 characters.\n- Offline `forge build` passed.\n- Offline `forge test`: 100 passed, zero failures.\n- No tracked files changed.","treeHash":null,"usage":{"cachedInputTokens":275584,"inputTokens":46516,"model":"gpt-6-astra","outputTokens":2641,"runtime":"codex","turns":3,"wallClockMs":167797}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"30a6c1a419ef4f9c","findings":[{"citation":"resolved","description":"`_normalReference()` reports `normal == false` whenever at least one burn has happened and more than STALE_AFTER_BLOCKS (50,400) blocks have passed since it, even if POOL4 is deployed, `marketOpen()` is true and `refTick()` answers. In that state the only thing that can make `normal` true again is a *successful* burn (it refreshes `lastBurnBlock`), but with `normal == false`: (a) `burnIMD(true, ..)` reverts Pool4Unavailable (line 213); (b) `burnIMD(false, ..)` is forced into fallback mode, which reads the plain pool (`_spot`, line 219) and guards against `startOfBlockAnchor`, an anchor that can only drift inside `lastRefTick +- FALLBACK_BAND` (lines 418-421); (c) `pokeAnchor()` only re-seeds from POOL4 when `normal` is true (lines 240-242), so the live POOL4 value is never consulted. The burn mechanism therefore deadlocks whenever a fallback burn cannot succeed: the plain pool (ETH/IMD, fee 10000, spacing 200, no hook) is not initialized or has no liquidity for a 0.01 ETH batch (PoolUnavailable / PartialFill), or the market has moved more than FALLBACK_BAND + MAX_REF_DEVIATION = 1,150 ticks below the stale reference (PriceOffReference, forever, because the anchor cannot follow). A week without a burn is routine (burnable must reach 0.002 ETH, i.e. 0.1 ETH of launch-pool volume above the cap), and an 11.5% drift of IMD against ETH over the contract's unbounded lifetime is likely, so this is a reachable permanent-breakage state, not an edge case. All fees above the cap accrued from then on sit in the hook's ERC-6909 claims with no sweep, so the post-retirement promise (every fee buys IMD and sends it to DEAD) is permanently broken. Recovery is only possible if a third party pays to push the plain pool's tick back up into the stale band (and the plain pool exists). Proposed minimal fix, preserving the spec's ordering and constants: let `pokeAnchor()` re-seed whenever `_readPool4()` returns available && open (regardless of staleness) and record `lastSeedBlock` in `_seedAnchor`; make `_normalReference()` treat a seed within STALE_AFTER_BLOCKS as fresh (e.g. `burnSpent == 0 || block.number - lastBurnBlock <= STALE_AFTER_BLOCKS || block.number - lastSeedBlock <= STALE_AFTER_BLOCKS`). The attached proof passes under exactly that change and fails on the current tree.","line":370,"path":"src/MedallionHook.sol","reproduction":"State: mainnet (chainid 1), POOL4 open with refTick 0, hook seeded, totalFees = 2 ETH (0.36 ETH burnable). 1) block 105: burnIMD(true, 0) succeeds, burnSpent = 0.05 ETH, lastBurnBlock = 105. 2) No burn for 50,401 blocks. POOL4 stays deployed, open and answering (optionally tracking the market: refTick = -2000). 3) Variant A: plain pool was never initialized on the PoolManager. Variant B: plain pool exists with deep liquidity but its tick is now -2000 (IMD got ~18% pricier in ETH). 4) Any caller: burnIMD(true, 0) -> revert Pool4Unavailable. burnIMD(false, 0) -> Variant A: revert PoolUnavailable; Variant B: revert PriceOffReference (spot -2000 < startOfBlockAnchor(>= -1000) - 150). pokeAnchor() -> Variant A: revert PoolUnavailable; Variant B: steps the anchor at most to lastRefTick - 1000 = -1000, never re-reads POOL4. Repeat for any number of blocks: identical result. Expected: with POOL4 live and open a burn is possible (normal mode, 0.05 ETH batch, reference -2000). Actual: no burn can ever succeed again, `normal` can never become true again, and the surplus (0.31 ETH here, growing with every future swap) is frozen in the hook's claims.","severity":"high","snippet":"        normal = available && open && (burnSpent == 0 || block.number - lastBurnBlock <= STALE_AFTER_BLOCKS);","title":"Stale-burn rule can permanently disable burnIMD/pokeAnchor while POOL4 is live: surplus fees frozen forever"},{"citation":"resolved","description":"When `pool4Seen` is already true, `_seedAnchor()` snapshots the *old* `anchorTick` into `startOfBlockAnchor` (via `_blockReference()`, lines 405-407) before overwriting `anchorTick`/`lastRefTick` with the fresh POOL4 reference. The first seed does the opposite (lines 400-404: snapshot := ref). If POOL4's `marketOpen()` flips to false later in the same block, `burnIMD(false, ..)` runs in fallback mode with `ref = _blockReference()` = the old anchor and `_stepAnchor` is a no-op (`anchorBlock == block.number`), so the one-sided guard and the 96% minimum output are computed from a reference that can be up to FALLBACK_BAND (1,000) ticks below the POOL4 value the hook validated moments earlier in the same block. The hook then buys IMD at a price up to ~11.5% worse than its own freshest trusted reference. Impact is bounded by the 0.01 ETH fallback batch (about 0.0012 ETH per occurrence, once per 5 blocks) and requires the market to close mid-block after a normal-mode poke/burn (possible when `marketOpen()` depends on a state transition rather than only on time), so severity is low. Fix: when a normal-mode seed happens, also set `startOfBlockAnchor = ref; snapshotBlock = block.number` (as the first-seed branch already does); a reference freshly read from POOL4 is strictly better than the stale start-of-block anchor.","line":406,"path":"src/MedallionHook.sol","reproduction":"Real PoolManager, chainid 1, hook seeded with anchor 0 / lastRef 0, 0.36 ETH burnable, plain pool liquid at tick 0. Same block: 1) market moves to tick 1000; POOL4.refTick() = 1000, marketOpen() = true; pokeAnchor() -> anchorTick = lastRefTick = 1000 but startOfBlockAnchor stays 0. 2) POOL4.marketOpen() becomes false (same block). 3) attacker pushes the plain pool down to tick -140. 4) burnIMD(false, 0): fallback, ref = 0, guard -140 >= 0 - 150 passes, minOut = 0.96 * quote(0.01 ETH, tick 0) = 0.0096 IMD. Measured output 0.009762 IMD sent to DEAD, versus 0.011052 IMD fair at the just-read reference 1000: the hook overpaid ~11.7% of a 0.01 ETH batch. Expected: the burn is guarded by / quoted against reference 1000 (reverting PriceOffReference at spot -140), as it would be in the next block.","severity":"low","snippet":"        } else {\n            _blockReference();\n        }","title":"A same-block POOL4 re-seed is ignored by fallback: burn is quoted against the previous anchor instead of the reference just read"}],"hash":"8844981e1556bcf61812adff89a9aaf6d3d392edbe2705b19291cb06c72b09f5","nodeId":"d2f9abac-42f5-4ad1-b3e3-8b425879b080","outcome":"completed","summary":"Review complete. The findings file is written and validated, the proof fails on the current tree and passes under a spec-preserving fix, and `src/` is byte-identical to the commit.\n\n**What I audited.** The Execution Trace, Periphery and First Principles passes over `src/MedallionHook.sol` and `src/FareToken.sol`, traced against the vendored v4-core (`Hooks.sol`, `PoolManager.sol`, `Pool.sol`, `StateLibrary.sol`). All four swap fee modes, claim minting and delta netting, retirement, the unlock-callback binding, the transient lock, status strings, and the token were traced with concrete values and probed on a real `PoolManager`. The existing 90-test suite passes.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **High: the stale-burn rule can permanently deadlock burning while POOL4 is live.** After one burn and 50,400 quiet blocks, `_normalReference()` at `src/MedallionHook.sol:370` reports \"not normal\" even though POOL4 is open and answering. The only way back is a successful fallback burn, but fallback is pinned to the plain pool and the stale anchor band. If the plain pool is absent or illiquid, or the market drifted more than 1,150 ticks below the stale reference, every `burnIMD` and `pokeAnchor` reverts forever and surplus fees are frozen. The proof at `test/scratch/StaleDeadlock.t.sol` reproduces both variants on a real PoolManager (fails with `Pool4Unavailable` now, passes after letting `pokeAnchor` re-seed from a live POOL4 and counting that seed as fresh).\n\n2. **Low: a same-block POOL4 re-seed is discarded by fallback.** `_seedAnchor` snapshots the previous anchor rather than the reference just read (`src/MedallionHook.sol:405-407`). If the market closes later in the same block, a fallback burn is guarded and quoted against a reference up to 1,000 ticks stale. Measured on a real manager: output 0.00976 IMD against 0.01105 fair at the fresh reference, on a 0.01 ETH batch.\n\n**Coverage.** All 12 listed entry points have rows, plus 4 invariant rows. Ten entry points hold; `burnIMD` and `pokeAnchor` carry finding 1. Slither and Aderyn leads (strict equality in `_stepAnchor`, reentrancy in `retire`, unused return in `_spot`) were traced and are intentional or guarded, so none became a finding.\n\n**Not reached.** Economic sizing of the fallback band and tolerance constants, and whether the mainnet POOL4 hook actually exposes `marketOpen()` and `refTick()` or whether the plain ETH/IMD pool exists, which only a mainnet fork can answer.","treeHash":null,"usage":{"cachedInputTokens":2535590,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":77435,"runtime":"claude","turns":48,"wallClockMs":981473}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"468e82a89b9bfe18","findings":[{"citation":"resolved","description":"Merged from audit_economics (high), audit_flow (high) and audit_permissions lead B (low): one root cause. _normalReference() returns normal=false whenever burnSpent>0 and block.number-lastBurnBlock>STALE_AFTER_BLOCKS, even if POOL4 has code, marketOpen()==true and refTick() validates. The only write to lastBurnBlock is a successful burnIMD (line 232), and the only writes to lastRefTick/anchorTick from POOL4 are in _seedAnchor, reached only when normal==true (lines 221, 242). In the stale state: burnIMD(true,..) reverts Pool4Unavailable (line 213); burnIMD(false,..) and pokeAnchor() use the plain pool only, reference = startOfBlockAnchor, and _stepAnchor clamps the anchor to lastRefTick +- FALLBACK_BAND (lines 418-421); the guard rejects spot < reference - MAX_REF_DEVIATION (line 227). So once the plain-pool tick is more than 1,150 ticks (~11%) below the lastRefTick recorded at the last burn, every fallback burn reverts PriceOffReference, lastBurnBlock can never advance, normal mode can never return, and all burnable fees (plus every fee accrued after retirement) sit as ERC-6909 claims in the hook with no sweep and no admin. The same structure stops burns if the plain pool (ETH/IMD, 10000, 200, no hook) is ever uninitialized or too shallow for a 0.01 ETH fill (PoolUnavailable / PartialFill). Mainnet today (block 26096992): POOL4 open, refTick 60281, plain pool spot 60314, so both pools exist; keepers are voluntary and unpaid (docs/operations.md), so a week without a call is ordinary and an 11% move in IMD/ETH over an unbounded lifetime is likely. In the opposite direction (spot far above lastRef+1000) the hook self-heals after one fallback burn but that one 0.01 ETH batch is exposed to a floor of 96% of quote(lastRef+1000) however high the market is. The code follows spec 7's wording ('no burn yet or one within STALE_AFTER_BLOCKS') literally, so the fix is a scope decision for the author; minimal options that preserve the rest of the design: (a) drop the burn-staleness clause so an available+open POOL4 always yields normal mode (the proof passes with exactly `normal = available && open;`), or (b) let pokeAnchor()/burnIMD re-seed anchorTick and lastRefTick from an available+open POOL4 regardless of staleness and count a seed as fresh.","line":370,"path":"src/MedallionHook.sol","reproduction":"State: chainid 1, POOL4 open with refTick 0, hook seeded (anchor=lastRef=0), launch pool accrued CAP+1 ETH so burnable=1 ETH, plain and POOL4 pools at tick 0. 1) block 105: burnIMD(true,0) succeeds: burnSpent=0.05 ETH, lastBurnBlock=105. 2) roll to 105+50401; set POOL4 refTick=-1500, POOL4 pool tick=-1500, plain pool tick=-1500 (POOL4 live, open, consistent with the market). 3) pokeAnchor() once per block for 30 blocks: lastRefTick stays 0, anchorTick settles at -1000. 4) burnIMD(true,0) -> revert Pool4Unavailable; burnIMD(false,0) -> revert PriceOffReference (-1500 < -1000-150); repeat in any later block: identical. Expected: with POOL4 available and open at least one route burns (burnSpent increases). Actual: burnSpent stays 0.05 ETH, burnable()=0.95 ETH unreachable. Run: forge test --offline --match-path test/scratch/StaleDeadlock.t.sol -> FAIL 'burns are deadlocked while POOL4 is live' on this tree; PASS in a copy where line 370 reads `normal = available && open;`.","severity":"high","snippet":"        normal = available && open && (burnSpent == 0 || block.number - lastBurnBlock <= STALE_AFTER_BLOCKS);","title":"Stale-burn rule disables a live POOL4 forever: after one burn and 50,400 idle blocks no burn route can recover once the plain spot sits >1,150 ticks below the last reference, stranding every surplus f"},{"citation":"resolved","description":"Merged from audit_math (low) and audit_permissions lead C (info). minOut is derived from the reference tick alone (line 228) and the guard only rejects spot < ref - tolerance (line 227; tolerance 300 for the plain pool in normal mode, else 150). When the market sits above the reference (POOL4's refTick lags the plain spot; on mainnet now refTick 60281 vs plain spot 60314), an attacker pushes the plain spot down to ref - 299, the permissionless burn fills at ~0.96*quote(ref), and the attacker unwinds: the hook pays a 0.05 ETH batch and receives up to ~8.7% less IMD than the pre-manipulation market (0.96/1.0001^500). A keeper who is also the dominant LP of the hookless plain pool can hold its spot at ref - 300 and capture ~4% per batch; in fallback mode pokeAnchor() can walk the anchor to lastRef - 1000 over five blocks for up to ~14% of each 0.01 ETH batch. All of this stays inside the constants spec 7 fixes (MAX_SLIPPAGE_BPS, MAX_PLAIN_DEVIATION, MAX_REF_DEVIATION, ANCHOR_STEP, FALLBACK_BAND), batch size and cadence are not caller-chosen, and the loss is bounded per batch and non-compounding (max ~0.0043 ETH per 0.05 ETH batch, once per 5 blocks). Reported as a design-limit note: a tightening (e.g. floor at 96% of quote(max(ref, spot at start of block)), or a smaller MAX_SLIPPAGE_BPS) is a spec decision for the author.","line":228,"path":"src/MedallionHook.sol","reproduction":"Mock manager filling at the pool's current tick with no LP fee (test/scratch/Leads.t.sol test_floorFromReferenceOnly): POOL4 open, refTick 0; launch pool accrued CAP+1 ETH; roll +5. Attacker sets the plain pool spot to -299 (guard passes: -299 >= 0-300). burnIMD(false,0): burnSpent=0.05 ETH, IMD sent to DEAD = 48527201690988745 (0.04853 IMD); minOut was 0.048 IMD. Fair value of 0.05 ETH at the unmanipulated market tick 500 = 0.05*1.0001^500 = 52563500000000000 (0.05256 IMD): shortfall 7.7% against the market, while MAX_SLIPPAGE_BPS reads as 4%. Same shape with a real PoolManager reported by audit_math (48039586998846135 out at spot -299 vs 52565000000000000 fair).","severity":"low","snippet":"        uint256 minOut = FullMath.mulDiv(_quote(amount, ref), 10_000 - MAX_SLIPPAGE_BPS, 10_000);","title":"burnIMD floor is 96% of quote(reference) only, and the price guard is one-sided, so a spot above the reference lets a sandwich or a keeper-controlled plain pool take ~4-9% of each batch"},{"citation":"resolved","description":"From audit_flow (low). When pool4Seen is already true, _seedAnchor() calls _blockReference() (line 406), which snapshots the OLD anchorTick into startOfBlockAnchor before anchorTick/lastRefTick are overwritten with the fresh POOL4 reference (lines 409-410). The first seed does the opposite (lines 403-404: snapshot := ref). If POOL4's marketOpen() flips to false later in the same block, burnIMD(false,..) runs in fallback with ref = startOfBlockAnchor (old anchor) and _stepAnchor is a no-op (anchorBlock == block.number, line 416), so the one-sided guard and the 96% floor use a reference that can be up to 1,000 ticks below the value the hook validated moments earlier. Impact is bounded by the 0.01 ETH fallback batch (~0.001 ETH per occurrence, once per 5 blocks) and needs the market to close mid-block after a normal-mode poke or burn, so severity is low. Fix: in the pool4Seen branch also set startOfBlockAnchor = ref and snapshotBlock = block.number, as the first-seed branch does; a reference freshly read from POOL4 is strictly better than the stale start-of-block anchor.","line":406,"path":"src/MedallionHook.sol","reproduction":"Mock manager filling at the pool's tick (test/scratch/Leads.t.sol test_sameBlockReseedIgnoredByFallback): hook seeded at anchor 0 / lastRef 0, burnable 1 ETH, block 105. Same block: 1) POOL4 refTick=1000, marketOpen=true, plain pool tick 1000; pokeAnchor() -> anchorTick=lastRefTick=1000 but startOfBlockAnchor()==0. 2) POOL4 marketOpen=false. 3) plain pool pushed to tick -140. 4) burnIMD(false,0): fallback, ref=0, guard -140 >= 0-150 passes, minOut=0.96*quote(0.01 ETH, tick 0)=0.0096 IMD. Actual output 9860982344853700 (0.00986 IMD) sent to DEAD versus 11051700000000000 (0.01105 IMD) fair at the reference 1000 just read: ~10.8% overpaid on a 0.01 ETH batch. Expected: revert PriceOffReference at spot -140 against reference 1000, as it would in the next block.","severity":"low","snippet":"            _blockReference();","title":"A normal-mode re-seed keeps the pre-seed anchor as the block's fallback reference, so a same-block fallback burn is guarded and quoted against a reference up to FALLBACK_BAND below the POOL4 value jus"},{"citation":"resolved","description":"From audit_permissions lead A. The constructor accepts any validated POOL4 answer, open or closed (lines 113-121: `if (available)`), while the only post-deployment seed path is _seedAnchor, reached only when _normalReference() is normal, which additionally requires open==true. A hook deployed while POOL4 was unreadable (or not yet deployed) therefore has no burn path until POOL4 reports marketOpen()==true once, even though the same POOL4 state would have seeded it at construction. docs/operations.md records the stricter post-deployment rule as intended and spec 7 says 'POOL4 never read: Pool4Unavailable' for pokeAnchor, so this is a documented asymmetry, not a defect; on mainnet POOL4 is open today so the constructor will seed. Recorded so the deployer knows the hook must be constructed while POOL4 answers.","line":244,"path":"src/MedallionHook.sol","reproduction":"test/scratch/Leads.t.sol test_constructorSeedsClosedMarketButPokeCannot: 1) POOL4 has no code; deploy hook A -> pool4Seen()==false. 2) Give POOL4 code with marketOpen()=false, refTick()=1000. 3) A.pokeAnchor() -> revert Pool4Unavailable (A.burnIMD(false,0) and burnIMD(true,0) likewise). 4) Deploy hook B under the identical POOL4 state: B.pool4Seen()==true, B.anchorTick()==1000. Expected per the documented design: exactly this; recorded as the operational precondition.","severity":"info","snippet":"            if (!pool4Seen) revert Pool4Unavailable();","title":"Constructor seeds the anchor from a closed-but-answering POOL4, but pokeAnchor()/burnIMD() never can after deployment"},{"citation":"resolved","description":"From audit_economics (low). afterInitialize binds launchPool permanently to the first pool whose currency0 is native ETH, with no check on currency1, fee or tickSpacing, and there is no setter by design. If the hook has code before the factory initializes the FARE447/ETH pool, anyone can initialize PoolKey(ETH, anyToken, 100, 1, hook) and the real FARE447 pool pays no fee forever: totalFees never reaches the cap, retire() always reverts NotRecouped, burnIMD() always NothingToBurn. Spec 2 mandates 'the first native-ETH pool becomes public launchPool', the IMD factory deploys the hook and initializes its pool in one transaction, and README/launch.json notes ask for exactly that, so this is an operational precondition that the service already meets rather than a code defect. Recorded for the deployer's checklist because the consequence of a non-atomic deployment is total and irreversible.","line":134,"path":"src/MedallionHook.sol","reproduction":"Real PoolManager, hook at a 0x10CC address, no pool initialized yet. Attacker calls manager.initialize(PoolKey(ETH, junkToken, 100, 1, hook), 2**96): afterInitialize line 134 sees launchPoolSet==false and currency0==0 -> launchPool = that id. Factory then calls manager.initialize(PoolKey(ETH, FARE447, 3000, 60, hook), 2**96): launchPoolSet is already true, nothing changes. Expected: the FARE447 pool is the fee pool. Actual: hook.launchPool() == id(ETH, junkToken, 100, 1, hook); swaps in the FARE447 pool take the _isLaunchPool==false path (lines 149, 162) and mint no claims. The existing test test_otherNativePoolRemainsFeeFree in test/HookFees.t.sol shows the second native pool is fee-free.","severity":"info","snippet":"        if (!launchPoolSet && Currency.unwrap(key.currency0) == address(0)) {","title":"launchPool is whichever native-ETH pool initializes first; only an atomic deploy-and-initialize prevents a stranger from binding an unrelated ETH pool as the sole fee pool"},{"citation":"resolved","description":"From audit_economics (low). retire() is the only path that releases the CREATOR reserve and hard-requires MEDALLION_NFT.ownerOf(447) to return a valid address (then the token to reach DEAD). An ERC-721 whose ownerOf reverts for a nonexistent token (OpenZeppelin behaviour) makes _medallionOwner return available=false once the token is burned by its holder or the contract is removed; from then on retire() reverts MedallionUnavailable every time, creatorPaid stays 0 and creatorEntitlement() keeps exactly 1.64 ETH of claims locked. burnIMD keeps working on the surplus. This matches spec 6 ('MedallionUnavailable on no code/bad return') and the fiction; recorded so the owner knows the reserve has no fallback if the NFT is destroyed rather than retired. Adding one (e.g. treating a burned token as retired) is a scope decision.","line":190,"path":"src/MedallionHook.sol","reproduction":"State: totalFees >= 1.64 ETH, MEDALLION_NFT.ownerOf(447) reverts or returns a non-address word, or MEDALLION_NFT has no code. retire() from any account -> revert MedallionUnavailable at line 190, repeatable forever; manager.balanceOf(hook,0) >= 1.64 ETH stays unreachable. The existing test test_retireNoNFTCodeAndMalformedOwnerFailClosed in test/RetireBurn.t.sol exercises both revert paths.","severity":"info","snippet":"        if (!available) revert MedallionUnavailable();","title":"If medallion #447 can no longer be resolved by ownerOf, retire() reverts MedallionUnavailable forever and the reserved 1.64 ETH has no other release path"}],"hash":"9f341d448624510ca5a8b3cf5e050df397bd25aa3a441835d8302b28b6e3e216","nodeId":"93dc332d-63b3-41d7-adbb-df25d0729c65","outcome":"completed","summary":"Review complete. The findings file is written and validated against the tree.\n\n**Outcome:** one high finding, two lows, three infos, all reproduced; coverage answers all 12 entry points plus five invariant rows.\n\n- **High (finding 1, `src/MedallionHook.sol:370`):** the stale-burn clause in the normal-mode test ignores a live, open POOL4 once a burn is older than 50,400 blocks. Fallback can only move the anchor within lastRef ± 1000 and refuses spot below reference − 150, so after a week without a keeper call and an ~11% move in IMD/ETH no burn route can ever succeed again, and every surplus fee strands in the hook's claims with no sweep. This merges the economics and flow highs and the permissions liveness lead, which share the root cause. I confirmed mainnet state today: POOL4 open at refTick 60281 and both burn pools live, so the price-drift variant is the reachable one. The attached proof test fails on the tree and passes in a copy with the clause removed. I noted that the code follows the spec's wording, so the fix is a scope decision.\n- **Low (finding 2, line 228):** the output floor uses the reference only and the guard is one-sided, so up to ~8% of a batch can be captured when the market sits above the reference. Merged audit_math with the permissions keeper note. Measured 7.7% shortfall in a mock.\n- **Low (finding 3, line 406):** a normal-mode re-seed keeps the pre-seed anchor as the block's fallback reference. Measured 10.8% overpay on a same-block fallback burn after the market closes.\n- **Info (findings 4 to 6):** closed-market seeding asymmetry, first-native-pool binding, and the stranded reserve if the NFT becomes unresolvable. All are spec-literal or documented, and I kept them so the record shows they were weighed.\n\nNothing new surfaced from my own pass over the swap accounting, retire path, unlock callback binding, ledger invariant, constants, status strings, manifest and token. The existing suite passes 128 of 128. The one row I left unreached is whether the live POOL4 hook accepts swaps from this hook as sender, which only affects route choice, not liveness.","treeHash":null,"usage":{"cachedInputTokens":1197893,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":42256,"runtime":"claude","turns":26,"wallClockMs":592302}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"98b4506bef931d13","findings":[{"citation":"resolved","description":"Precision x boundary seam. The price guard is one-sided (only spot below ref - tolerance reverts) and the output floor is 96% of quote(ref). When POOL4's refTick lags below the plain pool's spot (the reference is an external, slower-moving number: on mainnet today refTick is 60405 while plain spot is 60314, so it lags in either direction by ~100 ticks and can lag more after a move), the floor is computed from the lower reference, not from the better of reference and spot. A searcher can push the plain spot down to ref - 299 (allowed by MAX_PLAIN_DEVIATION = 300), let the permissionless burn execute at the 0.96*quote(ref) floor, and unwind. The hook then pays a batch worth quote(spot) and receives only 0.96*quote(ref). With spot 500 ticks above ref this is 0.96 / 1.0001^500 = 91.3% of fair, an 8.7% shortfall, repeatable every MIN_BLOCKS_BETWEEN_BURNS blocks while the lag persists (max ~0.0043 ETH per 0.05 ETH batch; bounded, non-compounding). This behaviour is exactly what the spec formula says (minOut >= quote(reference)*96%, one-sided guard), so it is reported as a design-limit note for the author, not a spec violation. Reading the spot at the start of the call would not help, because the sandwicher sets the spot in the same block; a genuine tightening needs a floor that cannot sit below the market (for example 96% of quote(max(ref, start-of-block anchor)) in normal mode, or a smaller MAX_SLIPPAGE_BPS), which is a spec decision for the author.","line":228,"path":"src/MedallionHook.sol","reproduction":"Real PoolManager, mainnet chainId. POOL4 mock answers marketOpen=true, refTick=0. Plain pool (ETH/IMD, 10000, 200, no hook) initialized at tick 500 with 1000 ether full-range liquidity. Launch pool accrues 2 ETH of fees (one 100 ETH exact-in buy), roll +5 blocks. Attacker swaps ETH->IMD on the plain pool with sqrtPriceLimit = getSqrtPriceAtTick(-299): slot0 tick becomes -299 (guard passes: -299 >= 0 - 300). Then anyone calls burnIMD(false, 0). Observed: burnSpent = 0.05 ether, IMD received by DEAD = 48039586998846135 (0.04804 IMD). Fair value of 0.05 ETH at the pre-manipulation spot 500 with no fee = 0.05 * 1.0001^500 = 52565000000000000 (0.05257 IMD). Shortfall 8.6%; minOut was 0.96 * quote(0) = 0.048 IMD. Expected by a reader of 'MAX_SLIPPAGE_BPS = 400': at most 4% below fair; actual: 4% below a reference that is itself 4.9% below the market.","severity":"low","snippet":"        if (int256(spot) < int256(ref) - tolerance) revert PriceOffReference();\n        uint256 minOut = FullMath.mulDiv(_quote(amount, ref), 10_000 - MAX_SLIPPAGE_BPS, 10_000);","title":"burnIMD minimum-output floor is anchored to the reference tick only, so a reference lagging below spot lets a sandwich take up to ~8.6% of each batch instead of 4%"}],"hash":"aa5cf88b0c394ae0f203beeb0d013d3c02f94a98ba057d4605cf9bac530f6ae9","nodeId":"aaaef1ef-33b2-4199-896a-c3fd19b0859e","outcome":"completed","summary":"The math audit is complete and the findings file is written. The working tree is clean apart from the ignored findings file, and the scratch tests are removed.\n\n**Outcome.** No material arithmetic defect was found in the assigned area. One low-severity, spec-consistent seam is recorded, with measured numbers, in `.imd-findings.json`.\n\n**What I verified**\n\n- **Swap fees, all four modes.** Traced the v4-core 1.0.2 delta path (`Hooks.beforeSwap`, `Hooks.afterSwap`, `Pool.swap`) and fuzzed 3000 runs against a real PoolManager from 1 wei to 1e21 wei. The ledger equals `|amount|/50` in the two specified-ETH modes and `gross/50` in the two unspecified modes, the user's ETH delta is exactly the specified amount, and the PartialFill equality is exact because the pool's remaining amount reaches zero on a full fill. The int128 range check precedes negation, so no cast can wrap.\n- **Quote arithmetic.** Both `_quote` branches and negative ticks were exercised with real burns at ticks -120000, 60405 and 460000. Outputs match `amount × 1.0001^tick` less the 1% LP fee. The high branch has no overflow even at the maximum tick, and ticks above about 443636 are unreachable for a 0.05 ETH batch since the IMD leg exceeds int128.\n- **Anchor stepping.** The band clamp and the step clamp keep the anchor between the last reference and the spot, so it can never leave the valid tick range. That lead is dead.\n- **Ledger and retirement.** `burnSpent` can never exceed `totalFees - CAP`, so the hook always holds at least the cap in claims when retirement is allowed. Status formatting truncates correctly at two and one decimals.\n- **External boundaries on mainnet.** Via the operator's keyed RPC: POOL4_HOOK answers `marketOpen` true and `refTick` 60405, both fixed burn pools are initialized, and the plain pool holds about 7.7e21 liquidity. The references and routes the burn math depends on are live.\n\n**The one finding (low).** The minimum-output floor is 96% of the quote at the reference tick, while the guard is one-sided. When the reference lags below the plain spot, a sandwich can push spot down to reference minus 299 ticks and let the burn complete at the reference floor. Measured on a real PoolManager with spot 500 ticks above reference: the hook received 0.04804 IMD for 0.05 ETH against 0.05257 fair, an 8.6% shortfall rather than the 4% a reader of `MAX_SLIPPAGE_BPS` expects. The loss is bounded per batch and non-compounding, and the formula is exactly what the spec prescribes, so it is recorded as a design-limit note for the author.\n\n**Coverage.** All 12 listed entry points have rows: 11 hold, `burnIMD` references the finding. Four extra rows cover the claims invariant, status formatting, both quote branches, and fee rounding direction. The static-analysis leads (strict block equality, reentrancy ordering in retire, uninitialized `fee`, unused slot0 return) were each checked and none is a defect.\n\n**Not reached.** The POOL4_HOOK's own fee schedule could not be read offline. If it charges more than about 1.8% on top of the LP fee, the POOL4 route would trip the Slippage floor whenever spot sits 150 ticks below reference. That is a liveness question for the deployer, not a loss.","treeHash":null,"usage":{"cachedInputTokens":2802529,"inputTokens":550,"model":"claude-fable-5-1","outputTokens":62693,"runtime":"claude","turns":39,"wallClockMs":822935}},{"artifacts":[],"attempt":2,"bundleHash":"c1cdd3a948ec1aeb2b55307aa4f6033b924befe2ba7bf0ea6caab68c89dd6193","device":"0256823ae36e7900","findings":[],"hash":"bb073f41c049deeacccee49a29d17c3cf0176f8262219ed6b571b0c7d427d683","nodeId":"192fa1ac-0a0e-460d-9fa2-3fd242edf967","outcome":"completed","summary":"Implemented FARE447, MedallionHook, offline dependencies, tests, and deployment documentation.\n\nVerified:\n\n- Offline build succeeds.\n- All 90 tests pass.\n- `forge fmt --check` passes.\n- Corrected `univ4_hook` manifest passes schema validation.\n- Flags are `0x10CC`; forbidden opcode count is zero.\n\nDeployment assumptions and responsibilities are documented in README.md and docs/. No transactions were broadcast.","treeHash":"26964f7f2454cef0cb5996cff5d561148b59de7b","usage":{"cachedInputTokens":2958592,"inputTokens":93430,"model":"gpt-6-astra","outputTokens":17598,"runtime":"codex","turns":8,"wallClockMs":1150595}},{"artifacts":[],"attempt":2,"bundleHash":"ec70f9f2f6b4d0b5c57e83921846c1180e49183eecd85cca29a969338f7e262a","device":"3f6a9bdd601cb99f","findings":[],"hash":"e686e9ac1998d382ab2ade7d80dd80d20dd28da3ab1abc06a4ba136c22262bff","nodeId":"7506939c-250a-4ca6-99a9-2c01bdfa8628","outcome":"completed","summary":"Updated only [launch.json](launch.json), clarifying Cancun compatibility, required hook address bits and mainnet-only operations.\n\nSchema constraints, constructor, metadata and permissions verified. Notes: 1,858/4,000 characters.\n\nOffline `forge build` passed; `forge test` passed all 100 tests.","treeHash":"83548986570f7b83955995c2900f0ba91ee25f59","usage":{"cachedInputTokens":284032,"inputTokens":41232,"model":"gpt-6-astra","outputTokens":3104,"runtime":"codex","turns":3,"wallClockMs":111719}}],"verification":[{"checks":[{"durationMs":3348,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.21s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:344:53\n    │\n344 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:347:47\n    │\n347 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:350:60\n    │\n350 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:351:17\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:50\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:57\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:55\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:62\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:35\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:42\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:53\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:60\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:81\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:88\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:367:17\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:329:9\n    │\n329 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:280:9\n    │\n280 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:155:63\n    │\n155 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:153:22\n    │\n153 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:25\n    │\n172 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:27\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:34\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:174:18\n    │\n174 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:194:46\n    │\n194 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n195 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n196 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:200:13\n    │\n200 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:205:9\n    │\n205 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:206:9\n    │\n206 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:417:9\n    │\n417 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:432:9\n    │\n432 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:236:9\n    │\n236 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:273:53\n    │\n273 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:277:42\n    │\n277 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:278:26\n    │\n278 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:322:25\n    │\n322 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:16\n    │\n323 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:23\n    │\n323 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:331:65\n    │\n331 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:351:31\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:367:38\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:430:22\n    │\n430 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:450:15\n    │\n450 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:455:39\n    │\n455 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:280:17\n    │\n280 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:524:17\n    │\n524 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:545:17\n    │\n545 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:636:17\n    │\n636 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:641:21\n    │\n641 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:665:17\n    │\n665 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n674 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS());\n    │         ───────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n682 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ───────────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:736:21\n    │\n736 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":565,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131486, ~: 132144)\nLogs:\n  Bound result 67598525614789486796195\n  Bound result 2475691\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 7.76ms (10.69ms CPU time)\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 908008)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 699832)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 423859)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 470096)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 717255)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 754788)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735202)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 713856)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 21.20ms (7.17ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 298167, ~: 301965)\nLogs:\n  Bound result 562\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299430)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286358)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253933)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240605)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23131)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352843)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120641)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300801)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286212)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299236)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287298)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518728)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 12716738)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 6023474)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561832)\n[PASS] test_permissionsAndFlags() (gas: 15392)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221614)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631771)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6662380)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 424030)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 49.60ms (82.07ms CPU time)\n\nRan 58 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 917130, ~: 887772)\nLogs:\n  Bound result 998000000000000087\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 529294)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 342274)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 555103)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 281005)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 627979)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 550029)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 527003)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 485104)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 418499)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 932896)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 609978)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 561006)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 460358)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 581290)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 599453)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1151618)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 250473)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32866)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 815203)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 891782)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 51938)\n[PASS] test_constructorSeedsClosedOracle() (gas: 15157941)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1522419)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 600448)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 645337)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 825814)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 16233187)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 15535043)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 15023307)\n[PASS] test_livePokeDoesNotSpendFeesOrResetBurnCooldown() (gas: 882518)\n[PASS] test_liveSeedFreshnessBoundaryIsInclusive() (gas: 723560)\n[PASS] test_liveSeedFreshnessExpiresBackToFallback() (gas: 770922)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 476565)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 15034249)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 448489)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 15024639)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302740)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 872205)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324508)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542105)\n[PASS] test_retireBelowCapReverts() (gas: 238844)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678569)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692521)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201380)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247412)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267105)\n[PASS] test_revertingOracleNeverSeeds() (gas: 15022961)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203396)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 15023334)\n[PASS] test_staleBoundaryIsInclusive() (gas: 621418)\n[PASS] test_staleClosedOracleFallbackPokesCannotRefreshFreshness() (gas: 1166682)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeDownwardMove() (gas: 1082316)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeUpwardMove() (gas: 1081240)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeDownwardMove() (gas: 1077824)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeUpwardMove() (gas: 1076812)\n[PASS] test_staleLivePokeRecoversWithoutInitializedPlainPool() (gas: 9670773)\n[PASS] test_staleMalformedOracleFallbackPokesCannotRefreshFreshness() (gas: 1146915)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 66.49ms (408.59ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 1045  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1003  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 456.85ms (446.60ms CPU time)\n\nRan 5 test suites in 458.68ms (601.89ms CPU time): 100 tests passed, 0 failed, 0 skipped (100 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":84,\"docs/security-review.md\":26,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":460,\"test/FareToken.t.sol\":131,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/RetireBurn.t.sol\":795,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1534,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/MedallionHook.sol:420: MedallionHook._stepAnchor(int24) (src/MedallionHook.sol#420-433) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MedallionHook.sol:187: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#187-207):\n[medium/medium] uninitialized-local at src/MedallionHook.sol:150: MedallionHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/MedallionHook.sol#150) is a local variable never initialized\n[medium/medium] unused-return at src/MedallionHook.sol:388: MedallionHook._spot(PoolKey) (src/MedallionHook.sol#388-392) ignores return value by (sqrtPrice,tick,None,None) = poolManager.getSlot0(key.toId()) (src/MedallionHook.sol#390)\n[low/medium] events-maths at src/MedallionHook.sol:253: MedallionHook.unlockCallback(bytes) (src/MedallionHook.sol#253-284) should emit an event for: \n[low/medium] reentrancy-benign at src/MedallionHook.sol:187: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#187-207):\n[low/medium] reentrancy-benign at src/MedallionHook.sol:253: Reentrancy in MedallionHook.unlockCallback(bytes) (src/MedallionHook.sol#253-284):\n[low/medium] reentrancy-events at src/MedallionHook.sol:326: Reentrancy in MedallionHook._collect(uint256) (src/MedallionHook.sol#326-332):","passed":true},{"durationMs":429,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/MedallionHook.sol:194: Reentrancy: State change after external call (2 places)\n[low] large-numeric-literal at src/MedallionHook.sol:28: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/FareToken.sol:26: Literal Instead of Constant (20 places)\n[low] missing-inheritance at src/FareToken.sol:8: Missing Inheritance\n[low] state-change-without-event at src/MedallionHook.sol:253: State Change Without Event\n[low] unchecked-return at src/MedallionHook.sol:204: Unchecked Return (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"30e057b90abed2545e4d5a53abc707bb4c44814c80659a0b24c5408563c9737b","verifiedTreeHash":"c27b75946fd226846908e6c870b4e57a6aea7337","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":3123,"exitCode":0,"name":"build","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.98s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:222:53\n    │\n222 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), bytes(\"\"));\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:225:47\n    │\n225 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:228:60\n    │\n228 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:229:17\n    │\n229 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:293:50\n    │\n293 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:293:57\n    │\n293 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:296:91\n    │\n296 │         if (!openOK || !tickOK || openData.length != 32 || tickData.length != 32) return (false, false, 0);\n    │                                                                                           ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:296:98\n    │\n296 │         if (!openOK || !tickOK || openData.length != 32 || tickData.length != 32) return (false, false, 0);\n    │                                                                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:303:99\n    │\n303 │         if (openWord > 1 || tickWord < TickMath.MIN_TICK || tickWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:303:106\n    │\n303 │         if (openWord > 1 || tickWord < TickMath.MIN_TICK || tickWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:304:17\n    │\n304 │         return (true, openWord == 1, int24(tickWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:180:9\n    │\n180 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:326:9\n    │\n326 │         emit AnchorUpdated(ref, ref, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:142:63\n    │\n142 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:182:65\n    │\n182 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:140:22\n    │\n140 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:157:25\n    │\n157 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:159:18\n    │\n159 │         _collect(uint128(fee));\n    │                  ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:42\n    │\n173 │             params.amountSpecified < 0 ? uint256(-(params.amountSpecified + 1)) + 1 : uint256(params.amountSpecified);\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:87\n    │\n173 │             params.amountSpecified < 0 ? uint256(-(params.amountSpecified + 1)) + 1 : uint256(params.amountSpecified);\n    │                                                                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:200:46\n    │\n200 │               (bool ok, bytes memory result) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n201 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n202 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:206:13\n    │\n206 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:211:9\n    │\n211 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:212:9\n    │\n212 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:229:31\n    │\n229 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:341:9\n    │\n341 │         emit AnchorUpdated(anchor, lastRef, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:259:9\n    │\n259 │         emit IMDBurned(viaPool4, amount, output);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:304:38\n    │\n304 │         return (true, openWord == 1, int24(tickWord));\n    │                                      ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:340:18\n    │\n340 │         anchor = int24(next);\n    │                  ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:381:51\n    │\n381 │             burnPool(viaPool4), SwapParams(true, -int256(amount), TickMath.MIN_SQRT_PRICE + 1), bytes(\"\")\n    │                                                   ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:383:41\n    │\n383 │         if (int256(delta.amount0()) != -int256(amount) || delta.amount1() <= 0) revert PartialFill();\n    │                                         ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:384:26\n    │\n384 │         uint256 output = uint128(delta.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:420:15\n    │\n420 │             ++digits;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:425:39\n    │\n425 │             result[--digits] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":401,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testFuzz_conservation(uint256,uint256) (runs: 256, μ: 132119, ~: 132746)\nLogs:\n  Bound result 474052424035422494799170732\n  Bound result 354321265667698659559040482\n\n[PASS] test_infiniteAllowanceAndSelfTransfer() (gas: 135987)\n[PASS] test_metadataAndSupply() (gas: 1046070)\n[PASS] test_noAdminSelectors() (gas: 174395)\n[PASS] test_transferAllowanceAndBurn() (gas: 273820)\n[PASS] test_zeroAddressesAndInsufficientBalance() (gas: 105788)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.59ms (11.06ms CPU time)\n\nRan 31 tests for test/MedallionHook.t.sol:MedallionHookTest\n[PASS] testFuzz_feeRoundingAndConservation(uint128,bool,bool) (runs: 256, μ: 206157, ~: 213898)\nLogs:\n  Bound result 348230293037746644256\n\n[PASS] test_allCallbacksRestrictedAndUnlockAuthenticated() (gas: 149040)\n[PASS] test_alreadyDeadStillPaysAndRejectingCreatorRollsBackNFT() (gas: 752320)\n[PASS] test_atomicRetirementAndEvents() (gas: 569512)\n[PASS] test_badOracleReturnsFallbackInsteadOfDecodeRevert() (gas: 942825)\n[PASS] test_beforeModesRejectPartialFillAndRollback() (gas: 255530)\n[PASS] test_burnItselfStepsAnchorButUsesOldReference() (gas: 520965)\n[PASS] test_burnOrderTooSoonThenUnavailableThenDust() (gas: 639734)\n[PASS] test_burnReentrancyIsBlocked() (gas: 474666)\n[PASS] test_constructorRejectsWrongAddress() (gas: 5907)\n[PASS] test_constructorSeedsClosedOracleAndPokeReseedsNormal() (gas: 9965590)\n[PASS] test_donationsDoNotRecoupOrBecomeBurnable() (gas: 275053)\n[PASS] test_fallbackAnchorSingleStepEvenAfterLongIdleAndBoundedBand() (gas: 1653179)\n[PASS] test_fallbackBatchAndNeverReadUnavailable() (gas: 7333160)\n[PASS] test_fourFeeModesAreETHClaimsWithoutPayout() (gas: 418105)\n[PASS] test_literalTransientSlotOne() (gas: 1284813)\n[PASS] test_minOutIncludesNoLPDiscountAndCallerCanTighten() (gas: 943019)\n[PASS] test_nftReentrancyIsBlocked() (gas: 431934)\n[PASS] test_noCodeSepoliaCannotRetireOrBurn() (gas: 229687)\n[PASS] test_normalBothFixedRoutesAndReserveBeforeRetirement() (gas: 965193)\n[PASS] test_normalGuardOneSidedAndRouteTolerance() (gas: 962187)\n[PASS] test_otherPoolsFreeAndInitializationAlwaysAcceptsManager() (gas: 5743877)\n[PASS] test_partialZeroFillRollback() (gas: 835860)\n[PASS] test_permissionsAndCode() (gas: 6808573)\n[PASS] test_quoteAtTickAndStatusOneDecimal() (gas: 658621)\n[PASS] test_recoupedEmittedOnceAndStatusesTruncate() (gas: 307075)\n[PASS] test_refusedRetirementRollsEverythingBack() (gas: 396143)\n[PASS] test_retireBeforeCapAndUnavailable() (gas: 458142)\n[PASS] test_staleSinceBurnSelectsFallbackAndNeverBurnedStaysNormal() (gas: 859472)\n[PASS] test_startOfBlockAnchorAlsoSetsMinimumOutput() (gas: 729353)\n[PASS] test_startOfBlockAnchorGuardsPokeThenBurn() (gas: 585736)\nSuite result: ok. 31 passed; 0 failed; 0 skipped; finished in 16.40ms (61.18ms CPU time)\n\nRan 6 tests for test/RealPoolManager.t.sol:RealPoolManagerTest\n[PASS] test_burnAndRetireRequireOwnUnlock() (gas: 1763069)\n[PASS] test_freshTokenOnlyPoolCanCollectClaimsWithNoETH() (gas: 11472851)\n[PASS] test_realBurnClaimsSettlementAndAtomicRetirement() (gas: 1990961)\n[PASS] test_realDonationsOtherPoolsAndUnwind() (gas: 1115572)\n[PASS] test_realFourModesSettleAndClaimExactFee() (gas: 891184)\n[PASS] test_realPartialBeforeModesRevertButUnspecifiedModesChargeActualFill() (gas: 545536)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 54.81ms (9.28ms CPU time)\n\nRan 1 test for test/LedgerInvariant.t.sol:LedgerInvariantTest\n[PASS]\nLedgerInvariantTest invariants:\n[PASS] invariant_claimsBackLedgerAndDonationsStaySeparate\n[PASS] invariant_retirementAtomicAndIMDOnlyToSink\n LedgerInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| LedgerHandler | advance  | 389   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| LedgerHandler | burn     | 435   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| LedgerHandler | donate   | 392   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| LedgerHandler | retire   | 396   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| LedgerHandler | trade    | 436   | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 65536\n  Bound result 538560014339967001\n  Bound result 11519\n  Bound result 2568944231658846063\n  Bound result 5557\n  Bound result 68\n  Bound result 11443\n  Bound result 97594197564712810\n  Bound result 453673100345792364\n  Bound result 9316\n  Bound result 2022058060\n  Bound result 4507428275667112834\n  Bound result 1051\n  Bound result 10418\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 301.95ms (288.22ms CPU time)\n\nRan 4 test suites in 303.82ms (378.75ms CPU time): 44 tests passed, 0 failed, 0 skipped (44 total tests)\n","passed":true},{"durationMs":62,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"README.md\":30,\"docs/DEPENDENCIES.md\":22,\"docs/OPERATIONS.md\":62,\"docs/SECURITY.md\":32,\"docs/VERIFICATION.md\":29,\"docs/check_release.py\":52,\"foundry.toml\":21,\"launch.json\":26,\"remappings.txt\":3,\"src/FareToken.sol\":76,\"src/MedallionHook.sol\":430,\"test/FareToken.t.sol\":85,\"test/LedgerInvariant.t.sol\":128,\"test/MedallionHook.t.sol\":579,\"test/RealPoolManager.t.sol\":161,\"test/TestBase.sol\":37,\"test/mocks/Dependencies.sol\":191,\"test/mocks/RealRouter.sol\":90},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1957,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/MedallionHook.sol:415: MedallionHook._uintString(uint256) (src/MedallionHook.sol#415-429) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MedallionHook.sol:329: MedallionHook._stepAnchor(int24) (src/MedallionHook.sol#329-342) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MedallionHook.sol:195: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#195-213):\n[medium/medium] uninitialized-local at src/MedallionHook.sol:137: MedallionHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/MedallionHook.sol#137) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MedallionHook.sol:417: MedallionHook._uintString(uint256).digits (src/MedallionHook.sol#417) is a local variable never initialized\n[medium/medium] unused-return at src/MedallionHook.sol:307: MedallionHook._spot(PoolKey) (src/MedallionHook.sol#307-311) ignores return value by (price,spot,None,None) = poolManager.getSlot0(key.toId()) (src/MedallionHook.sol#308)\n[low/medium] reentrancy-benign at src/MedallionHook.sol:195: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#195-213):\n[low/medium] reentrancy-benign at src/MedallionHook.sol:234: Reentrancy in MedallionHook.burnIMD(bool,uint256) (src/MedallionHook.sol#234-260):\n[low/medium] reentrancy-events at src/MedallionHook.sol:177: Reentrancy in MedallionHook._collect(uint256) (src/MedallionHook.sol#177-183):","passed":true},{"durationMs":510,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/MedallionHook.sol:200: Reentrancy: State change after external call\n[low] large-numeric-literal at src/MedallionHook.sol:28: Large Numeric Literal (6 places)\n[low] literal-instead-of-constant at src/FareToken.sol:21: Literal Instead of Constant (19 places)\n[low] missing-inheritance at src/FareToken.sol:5: Missing Inheritance\n[low] state-change-without-event at src/MedallionHook.sol:124: State Change Without Event\n[low] unchecked-return at src/MedallionHook.sol:210: Unchecked Return","passed":true}],"detail":"launch.json is not a valid launch manifest: kind: Invalid discriminator value. Expected 'univ4_hook' | 'evm_project' | 'custom_token'","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"3326adc9087b3773b84563bbe09849bcf74ea923b2b3e04c8a6605797c85d7e7","verifiedTreeHash":"76dc5351ed1f47dcbff04476b471c440f1ba678f","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":5221,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.02s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:342:53\n    │\n342 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:345:47\n    │\n345 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:348:60\n    │\n348 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:349:17\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:50\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:57\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:55\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:62\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:35\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:42\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:53\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:60\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:81\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:88\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:365:17\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:327:9\n    │\n327 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:278:9\n    │\n278 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:153:63\n    │\n153 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:151:22\n    │\n151 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:170:25\n    │\n170 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:27\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:34\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:18\n    │\n172 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:192:46\n    │\n192 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n193 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n194 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:198:13\n    │\n198 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:203:9\n    │\n203 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:204:9\n    │\n204 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:412:9\n    │\n412 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:427:9\n    │\n427 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:234:9\n    │\n234 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:271:53\n    │\n271 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:275:42\n    │\n275 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:276:26\n    │\n276 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:320:25\n    │\n320 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:16\n    │\n321 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:23\n    │\n321 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:329:65\n    │\n329 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:349:31\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:365:38\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:425:22\n    │\n425 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:445:15\n    │\n445 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:450:39\n    │\n450 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:278:17\n    │\n278 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:522:17\n    │\n522 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:586:21\n    │\n586 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":683,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131336, ~: 132064)\nLogs:\n  Bound result 487327015492925896472181\n  Bound result 487327015492925896472178\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 7.24ms (11.96ms CPU time)\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 897292)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 694530)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 418490)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 467614)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 706517)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 749464)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735358)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 714034)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 38.70ms (6.23ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 297213, ~: 301813)\nLogs:\n  Bound result 1001600629\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299320)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286248)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253823)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240495)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23011)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352733)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120597)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300691)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286102)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299126)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287188)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518508)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 10245152)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 9883035)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561700)\n[PASS] test_permissionsAndFlags() (gas: 15502)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221526)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631639)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6614334)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 423986)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 42.62ms (96.55ms CPU time)\n\nRan 48 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 914184, ~: 882196)\nLogs:\n  Bound result 270154880325196003\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 523705)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 336751)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 550249)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 278435)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 618389)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 544506)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 521480)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 479757)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 415973)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 922180)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 604433)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 550356)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 455034)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 570685)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 594018)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1135621)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 245619)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32799)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 804443)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 886370)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 41593)\n[PASS] test_constructorSeedsClosedOracle() (gas: 18007403)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1523986)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 598073)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 640546)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 821171)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 19087962)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 18392389)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 17903013)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 474105)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 17911693)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 443120)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 17904389)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302823)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 858489)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324603)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542129)\n[PASS] test_retireBelowCapReverts() (gas: 238779)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678636)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692810)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201558)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247479)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267040)\n[PASS] test_revertingOracleNeverSeeds() (gas: 17902733)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203418)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 17903106)\n[PASS] test_staleBoundaryIsInclusive() (gas: 611852)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 139.82ms (645.21ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 984   | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1064  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 539.00ms (523.64ms CPU time)\n\nRan 5 test suites in 542.23ms (767.37ms CPU time): 90 tests passed, 0 failed, 0 skipped (90 total tests)\n","passed":true},{"durationMs":89,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":82,\"docs/security-review.md\":23,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":455,\"test/FareToken.t.sol\":131,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/RetireBurn.t.sol\":645,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"37d3850be6025bd2b0fa3d60ffbe1dc074276064de235a850dcdbfbabd263a66","verifiedTreeHash":"110417f9296d75bbe7b913c79ace975da3c5a87d","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":5209,"exitCode":0,"name":"build","output":"Compiling 84 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.06s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:342:53\n    │\n342 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:345:47\n    │\n345 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:348:60\n    │\n348 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:349:17\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:50\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:57\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:55\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:62\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:35\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:42\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:53\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:60\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:81\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:88\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:365:17\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:327:9\n    │\n327 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:278:9\n    │\n278 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:153:63\n    │\n153 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:151:22\n    │\n151 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:170:25\n    │\n170 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:27\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:34\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:18\n    │\n172 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:192:46\n    │\n192 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n193 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n194 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:198:13\n    │\n198 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:203:9\n    │\n203 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:204:9\n    │\n204 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:412:9\n    │\n412 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:427:9\n    │\n427 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:234:9\n    │\n234 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:271:53\n    │\n271 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:275:42\n    │\n275 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:276:26\n    │\n276 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:320:25\n    │\n320 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:16\n    │\n321 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:23\n    │\n321 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:329:65\n    │\n329 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:349:31\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:365:38\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:425:22\n    │\n425 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:445:15\n    │\n445 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:450:39\n    │\n450 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/BurnGuards.t.sol:307:21\n    │\n307 │             vm.roll(block.number + 5);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/BurnGuards.t.sol:328:21\n    │\n328 │             vm.roll(block.number + 5);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:278:17\n    │\n278 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:522:17\n    │\n522 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:586:21\n    │\n586 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":4400,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 897292)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 694530)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 418490)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 467614)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 706517)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 749464)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735358)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 714034)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 37.58ms (3.32ms CPU time)\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131283, ~: 132084)\nLogs:\n  Bound result 485697492670171545536635056\n  Bound result 130262462759456374277862196\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 43.19ms (30.08ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 1000  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1048  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 441.90ms (431.61ms CPU time)\n\nRan 48 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 917279, ~: 947308)\nLogs:\n  Bound result 998000000000005923\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 523705)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 336751)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 550249)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 278435)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 618389)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 544506)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 521480)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 479757)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 415973)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 922180)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 604433)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 550356)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 455034)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 570685)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 594018)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1135621)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 245619)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32799)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 804443)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 886370)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 41593)\n[PASS] test_constructorSeedsClosedOracle() (gas: 18007403)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1523986)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 598073)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 640546)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 821171)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 19087962)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 18392389)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 17903013)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 474105)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 17911693)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 443120)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 17904389)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302823)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 858489)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324603)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542129)\n[PASS] test_retireBelowCapReverts() (gas: 238779)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678636)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692810)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201558)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247479)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267040)\n[PASS] test_revertingOracleNeverSeeds() (gas: 17902733)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203418)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 17903106)\n[PASS] test_staleBoundaryIsInclusive() (gas: 611852)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 743.41ms (467.64ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 297638, ~: 301833)\nLogs:\n  Bound result 100\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299320)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286248)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253823)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240495)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23011)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352733)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120597)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300691)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286102)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299126)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287188)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518508)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 10245152)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 9883035)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561700)\n[PASS] test_permissionsAndFlags() (gas: 15502)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221526)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631639)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6614334)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 423986)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 749.11ms (131.37ms CPU time)\n\nRan 14 tests for test/RetirementSecurity.t.sol:RetirementSecurityTest\n[PASS] testFuzz_ownerOfRejectsDirtyAddressWords(uint96,address,bool) (runs: 512, μ: 203570, ~: 219860)\nLogs:\n  Bound result 598818210005744755228890\n\n[PASS] testFuzz_ownerOfRejectsEveryNonWordLength(uint8,bool) (runs: 512, μ: 202728, ~: 208468)\n[PASS] testFuzz_ownerOfRevertPayloadCannotEscapeRetirementError(bytes32,bool) (runs: 512, μ: 249507, ~: 267161)\n[PASS] testFuzz_transferRevertPreservesExactReturndata(bytes) (runs: 512, μ: 247923, ~: 240455)\n[PASS] testFuzz_unlockHashBindsEveryField(uint8) (runs: 512, μ: 8671448, ~: 8671542)\nLogs:\n  Bound result 0\n\n[PASS] test_creatorRejectionRestoresNFTClaimsUnlockAndPermitsRetry() (gas: 1138755)\n[PASS] test_crossEntryBlockedFromNFTAndCreatorAndLockClearsForBurn() (gas: 1261980)\n[PASS] test_managerCannotInvokeUnsolicitedUnlockBeforeOrAfterRetirement() (gas: 379099)\n[PASS] test_missingUnlockCallbackRollsBackAndCanRetry() (gas: 8666478)\n[PASS] test_ownerOfRejectsZeroBeforeAndAfterTransfer() (gas: 404891)\n[PASS] test_ownerOfUsesStaticcallAndCannotWriteState() (gas: 156002)\n[PASS] test_postTransferOwnerMustBeDeadAndFailureCanBeRetriedSameTransaction() (gas: 508251)\n[PASS] test_rejectedAlteredCallbackDoesNotConsumeHonestRequest() (gas: 8447401)\n[PASS] test_unlockReplayCannotBurnClaimsOrPayCreatorTwice() (gas: 8446615)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 757.71ms (2.22s CPU time)\n\nRan 6 tests for test/FareTokenProperties.t.sol:FareTokenAllowancePropertiesTest\n[PASS] testFuzz_delegatedSelfTransferConsumesFiniteBudgetWithoutChangingBalance(uint256) (runs: 1000, μ: 114050, ~: 114007)\nLogs:\n  Bound result 270271\n\n[PASS] testFuzz_failedDelegatedBurnDoesNotConsumeFiniteApproval(uint256,uint256) (runs: 1000, μ: 171044, ~: 171148)\nLogs:\n  Bound result 100000000000000000000000\n  Bound result 115792089237316195423570985008687907853269984665640563939457584007913129639934\n\n[PASS] testFuzz_finiteAllowanceBudgetSharedByTransferAndBurn(uint256,uint256) (runs: 1000, μ: 218683, ~: 219117)\nLogs:\n  Bound result 10050\n  Bound result 3395723174\n\n[PASS] testFuzz_revocationStopsOldSpenderAndDoesNotAffectAnother(uint256,bool) (runs: 1000, μ: 246985, ~: 246975)\nLogs:\n  Bound result 8260\n\n[PASS] testFuzz_zeroRecipientFailureRestoresSpentApproval(uint256) (runs: 1000, μ: 114458, ~: 114467)\nLogs:\n  Bound result 270271\n\n[PASS] test_maxMinusOneAllowanceIsFiniteEvenWhenBurningEntireSupply() (gas: 155858)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 757.75ms (2.43s CPU time)\n\nRan 15 tests for test/BurnGuards.t.sol:BurnGuardsTest\n[PASS] testFuzz_eachOracleSelectorRequiresExactlyOneABIWord(uint8,bool) (runs: 256, μ: 539758, ~: 542405)\nLogs:\n  Bound result 39\n\n[PASS] testFuzz_invalidSpotTickFailsBeforeUnlock(int24) (runs: 256, μ: 276870, ~: 276892)\nLogs:\n  Bound result 7501334\n\n[PASS] testFuzz_marketOpenRejectsNonCanonicalFullWord(uint256) (runs: 256, μ: 534971, ~: 534921)\nLogs:\n  Bound result 274717474507\n\n[PASS] testFuzz_referenceRejectsFullWordOutsideTickRange(uint256,bool) (runs: 256, μ: 564267, ~: 564192)\nLogs:\n  Bound result 2389460032482350027337533150322870887700808477268913758320\n\n[PASS] test_badReferenceAtConstructionCannotEnableFallbackButCanRecover() (gas: 9025806)\n[PASS] test_eachOracleSelectorMayRevertIndependently() (gas: 765934)\n[PASS] test_extremeValidReferencesAreAcceptedAndSeedExactly() (gas: 255302)\n[PASS] test_normalReseedDoesNotRewriteExistingStartOfBlockFallbackReference() (gas: 752237)\n[PASS] test_partialFillPrecedesSlippageAndCannotMutateAnchor() (gas: 1075926)\n[PASS] test_precedenceAvailabilityBeforeBudgetAndBudgetBeforePoolReads() (gas: 337644)\n[PASS] test_quoteFloorAtPositiveAndNegativeTicksToSingleWei() (gas: 2432971)\n[PASS] test_quotePrecisionAcrossSquareRoot128BitBoundary() (gas: 1458188)\n[PASS] test_referenceDirtySignExtensionIsRejected() (gas: 553132)\n[PASS] test_shortMarketResponseCannotCountAsPreviouslyReadClosedPool() (gas: 8192086)\n[PASS] test_slippageRevertsAnchorReseedAndCanRetryInSameBlock() (gas: 1035009)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 757.84ms (1.59s CPU time)\n\nRan 2 tests for test/MedallionLifecycleInvariant.t.sol:MedallionLifecycleInvariantTest\n[PASS]\nMedallionLifecycleInvariantTest invariants:\n[PASS] invariant_everyUnlockSettlesAllCurrencyDeltas\n[PASS] invariant_feeClaimsConserveAllFlows\n[PASS] invariant_retirementAndBurnsReachOnlyFixedRecipients\n MedallionLifecycleInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------------+----------------------+-------+---------+----------╮\n| Contract                  | Selector             | Calls | Reverts | Discards |\n+===============================================================================+\n| MedallionLifecycleHandler | attemptRetire        | 2360  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | burn                 | 2299  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | donate               | 2331  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | partialSwap          | 2411  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | poke                 | 2383  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | trade                | 2289  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | unauthorizedCallback | 2311  | 0       | 0        |\n╰---------------------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 7979\n  Bound result 34264337789633652473\n  Bound result 34264337789633652473\n  Bound result 34264337789633652473\n  Bound result 103\n  Bound result 5571\n  Bound result 8888\n  Bound result 1596\n  Bound result 2750\n  Bound result 2870\n  Bound result 7549\n  Bound result 4116\n  Bound result 11509\n  Bound result 2264337567134562831\n  Bound result 2264337567134562831\n  Bound result 9439\n  Bound result 34264337789633652473\n\n[PASS] test_handlerReachesBurnsRetirementAndAtomicFailures() (gas: 3345550)\nLogs:\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.38s (3.32s CPU time)\n\nRan 1 test for test/FareTokenProperties.t.sol:FareTokenStatefulPropertiesTest\n[PASS]\nFareTokenStatefulPropertiesTest invariants:\n[PASS] invariant_approvalsAreIsolatedAndOnlySuccessfulSpendingConsumesThem\n[PASS] invariant_everyIssuedUnitIsHeldOrBurned\n FareTokenStatefulPropertiesTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭------------------------+----------+-------+---------+----------╮\n| Contract               | Selector | Calls | Reverts | Discards |\n+================================================================+\n| FareTokenActionHandler | approve  | 4036  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | burn     | 4026  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | spend    | 4097  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | transfer | 4225  | 0       | 0        |\n╰------------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.30s (4.29s CPU time)\n\nRan 10 test suites in 4.30s (11.96s CPU time): 128 tests passed, 0 failed, 0 skipped (128 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":82,\"docs/security-review.md\":23,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":455,\"test/BurnGuards.t.sol\":333,\"test/FareToken.t.sol\":131,\"test/FareTokenProperties.t.sol\":268,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/MedallionLifecycleInvariant.t.sol\":380,\"test/PROPERTIES.md\":23,\"test/RetireBurn.t.sol\":645,\"test/RetirementSecurity.t.sol\":418,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4139c3d81ddd637d87db7c8f02c559c0653b8550b83341e7e1dd7df393a5f6c3","verifiedTreeHash":"089214800f587a1d006aa7937886a030d817b740","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":6388,"exitCode":0,"name":"build","output":"Compiling 87 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.25s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:344:53\n    │\n344 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:347:47\n    │\n347 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:350:60\n    │\n350 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:351:17\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:50\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:57\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:55\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:62\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:35\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:42\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:53\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:60\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:81\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:88\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:367:17\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:329:9\n    │\n329 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:280:9\n    │\n280 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:155:63\n    │\n155 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:153:22\n    │\n153 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:25\n    │\n172 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:27\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:34\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:174:18\n    │\n174 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:194:46\n    │\n194 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n195 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n196 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:200:13\n    │\n200 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:205:9\n    │\n205 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:206:9\n    │\n206 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:417:9\n    │\n417 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:432:9\n    │\n432 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:236:9\n    │\n236 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:273:53\n    │\n273 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:277:42\n    │\n277 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:278:26\n    │\n278 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:322:25\n    │\n322 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:16\n    │\n323 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:23\n    │\n323 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:331:65\n    │\n331 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:351:31\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:367:38\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:430:22\n    │\n430 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:450:15\n    │\n450 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:455:39\n    │\n455 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/BurnGuards.t.sol:307:21\n    │\n307 │             vm.roll(block.number + 5);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/BurnGuards.t.sol:328:21\n    │\n328 │             vm.roll(block.number + 5);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:280:17\n    │\n280 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:524:17\n    │\n524 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:545:17\n    │\n545 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:636:17\n    │\n636 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:641:21\n    │\n641 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:665:17\n    │\n665 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n674 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS());\n    │         ───────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n682 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ───────────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:736:21\n    │\n736 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":4357,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 908008)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 699832)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 423859)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 470096)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 717255)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 754788)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735202)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 713856)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 23.92ms (3.03ms CPU time)\n\nRan 11 tests for test/Pool4Route.t.sol:Pool4RouteTest\n[PASS] test_callerMinimumAboveRealOutputFailsWithoutSideEffects() (gas: 576927)\n[PASS] test_closedMarketRefusesPool4RouteAndFallbackUsesPlainPoolOnly() (gas: 341907)\n[PASS] test_fallbackAnchorNeedsOneBlockPerStepAndStaysInsideBand() (gas: 1533301)\n[PASS] test_plainToleranceIsWiderInNormalModeThanInFallback() (gas: 824220)\n[PASS] test_pool4GuardRejectsSpotBelowReferenceAndAcceptsSpotAbove() (gas: 955250)\n[PASS] test_pool4HookRevertAbortsBurnAtomicallyAndPlainRouteStillWorks() (gas: 581032)\n[PASS] test_pool4RouteSettlesThroughRealManagerWithFixedKey() (gas: 532552)\n[PASS] test_shortOracleWordOnRealManagerIsFallbackNotRevert() (gas: 296253)\n[PASS] test_staleBurnFallsBackUntilLivePokeRestoresPool4() (gas: 726062)\n[PASS] test_thinPool4LiquidityTripsSlippageFloorAndRollsBackSeed() (gas: 912576)\n[PASS] test_uninitializedPool4PoolFailsBeforeAnyUnlock() (gas: 11337652)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 34.96ms (10.89ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 297537, ~: 301941)\nLogs:\n  Bound result 4087929374563829359\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299430)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286358)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253933)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240605)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23131)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352843)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120641)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300801)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286212)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299236)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287298)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518728)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 12716738)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 6023474)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561832)\n[PASS] test_permissionsAndFlags() (gas: 15392)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221614)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631771)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6662380)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 424030)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 73.76ms (100.32ms CPU time)\n\nRan 58 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 917859, ~: 887772)\nLogs:\n  Bound result 713284935391646160\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 529294)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 342274)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 555103)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 281005)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 627979)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 550029)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 527003)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 485104)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 418499)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 932896)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 609978)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 561006)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 460358)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 581290)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 599453)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1151618)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 250473)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32866)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 815203)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 891782)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 51938)\n[PASS] test_constructorSeedsClosedOracle() (gas: 15157941)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1522419)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 600448)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 645337)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 825814)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 16233187)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 15535043)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 15023307)\n[PASS] test_livePokeDoesNotSpendFeesOrResetBurnCooldown() (gas: 882518)\n[PASS] test_liveSeedFreshnessBoundaryIsInclusive() (gas: 723560)\n[PASS] test_liveSeedFreshnessExpiresBackToFallback() (gas: 770922)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 476565)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 15034249)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 448489)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 15024639)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302740)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 872205)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324508)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542105)\n[PASS] test_retireBelowCapReverts() (gas: 238844)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678569)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692521)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201380)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247412)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267105)\n[PASS] test_revertingOracleNeverSeeds() (gas: 15022961)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203396)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 15023334)\n[PASS] test_staleBoundaryIsInclusive() (gas: 621418)\n[PASS] test_staleClosedOracleFallbackPokesCannotRefreshFreshness() (gas: 1166682)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeDownwardMove() (gas: 1082316)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeUpwardMove() (gas: 1081240)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeDownwardMove() (gas: 1077824)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeUpwardMove() (gas: 1076812)\n[PASS] test_staleLivePokeRecoversWithoutInitializedPlainPool() (gas: 9670773)\n[PASS] test_staleMalformedOracleFallbackPokesCannotRefreshFreshness() (gas: 1146915)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 103.70ms (369.21ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 1028  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1020  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 430.18ms (415.89ms CPU time)\n\nRan 15 tests for test/BurnGuards.t.sol:BurnGuardsTest\n[PASS] testFuzz_eachOracleSelectorRequiresExactlyOneABIWord(uint8,bool) (runs: 256, μ: 543658, ~: 547259)\nLogs:\n  Bound result 44\n\n[PASS] testFuzz_invalidSpotTickFailsBeforeUnlock(int24) (runs: 256, μ: 279360, ~: 279374)\nLogs:\n  Bound result 7499705\n\n[PASS] testFuzz_marketOpenRejectsNonCanonicalFullWord(uint256) (runs: 256, μ: 539829, ~: 539787)\nLogs:\n  Bound result 186977690902520050890815680924978118959012669708973170337249224\n\n[PASS] testFuzz_referenceRejectsFullWordOutsideTickRange(uint256,bool) (runs: 256, μ: 569159, ~: 569046)\nLogs:\n  Bound result 86512234426819975560610238309969401904473193869072815701199808395615\n\n[PASS] test_badReferenceAtConstructionCannotEnableFallbackButCanRecover() (gas: 7763293)\n[PASS] test_eachOracleSelectorMayRevertIndependently() (gas: 772734)\n[PASS] test_extremeValidReferencesAreAcceptedAndSeedExactly() (gas: 255346)\n[PASS] test_normalReseedDoesNotRewriteExistingStartOfBlockFallbackReference() (gas: 759957)\n[PASS] test_partialFillPrecedesSlippageAndCannotMutateAnchor() (gas: 1080827)\n[PASS] test_precedenceAvailabilityBeforeBudgetAndBudgetBeforePoolReads() (gas: 344915)\n[PASS] test_quoteFloorAtPositiveAndNegativeTicksToSingleWei() (gas: 2471847)\n[PASS] test_quotePrecisionAcrossSquareRoot128BitBoundary() (gas: 1478276)\n[PASS] test_referenceDirtySignExtensionIsRejected() (gas: 557986)\n[PASS] test_shortMarketResponseCannotCountAsPreviouslyReadClosedPool() (gas: 6904820)\n[PASS] test_slippageRevertsAnchorReseedAndCanRetryInSameBlock() (gas: 1045791)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 736.36ms (838.79ms CPU time)\n\nRan 14 tests for test/RetirementSecurity.t.sol:RetirementSecurityTest\n[PASS] testFuzz_ownerOfRejectsDirtyAddressWords(uint96,address,bool) (runs: 512, μ: 204801, ~: 219805)\nLogs:\n  Bound result 79228162514264337593543950335\n\n[PASS] testFuzz_ownerOfRejectsEveryNonWordLength(uint8,bool) (runs: 512, μ: 202970, ~: 208401)\n[PASS] testFuzz_ownerOfRevertPayloadCannotEscapeRetirementError(bytes32,bool) (runs: 512, μ: 249166, ~: 231372)\n[PASS] testFuzz_transferRevertPreservesExactReturndata(bytes) (runs: 512, μ: 249693, ~: 240388)\n[PASS] testFuzz_unlockHashBindsEveryField(uint8) (runs: 512, μ: 8383333, ~: 8383435)\nLogs:\n  Bound result 0\n\n[PASS] test_creatorRejectionRestoresNFTClaimsUnlockAndPermitsRetry() (gas: 1138577)\n[PASS] test_crossEntryBlockedFromNFTAndCreatorAndLockClearsForBurn() (gas: 1264915)\n[PASS] test_managerCannotInvokeUnsolicitedUnlockBeforeOrAfterRetirement() (gas: 379098)\n[PASS] test_missingUnlockCallbackRollsBackAndCanRetry() (gas: 8378350)\n[PASS] test_ownerOfRejectsZeroBeforeAndAfterTransfer() (gas: 404757)\n[PASS] test_ownerOfUsesStaticcallAndCannotWriteState() (gas: 156046)\n[PASS] test_postTransferOwnerMustBeDeadAndFailureCanBeRetriedSameTransaction() (gas: 508095)\n[PASS] test_rejectedAlteredCallbackDoesNotConsumeHonestRequest() (gas: 8159383)\n[PASS] test_unlockReplayCannotBurnClaimsOrPayCreatorTwice() (gas: 8158597)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 736.43ms (2.18s CPU time)\n\nRan 6 tests for test/FareTokenProperties.t.sol:FareTokenAllowancePropertiesTest\n[PASS] testFuzz_delegatedSelfTransferConsumesFiniteBudgetWithoutChangingBalance(uint256) (runs: 1000, μ: 114000, ~: 114007)\nLogs:\n  Bound result 436200881207263\n\n[PASS] testFuzz_failedDelegatedBurnDoesNotConsumeFiniteApproval(uint256,uint256) (runs: 1000, μ: 170958, ~: 171136)\nLogs:\n  Bound result 733769501084391857644515518\n  Bound result 4808960216601957399556084957911059402\n\n[PASS] testFuzz_finiteAllowanceBudgetSharedByTransferAndBurn(uint256,uint256) (runs: 1000, μ: 218543, ~: 218883)\nLogs:\n  Bound result 91183373408842328448728912\n  Bound result 852922794374465671991276438\n\n[PASS] testFuzz_revocationStopsOldSpenderAndDoesNotAffectAnother(uint256,bool) (runs: 1000, μ: 246925, ~: 246975)\nLogs:\n  Bound result 14240425048859897617266\n\n[PASS] testFuzz_zeroRecipientFailureRestoresSpentApproval(uint256) (runs: 1000, μ: 114396, ~: 114467)\nLogs:\n  Bound result 558\n\n[PASS] test_maxMinusOneAllowanceIsFiniteEvenWhenBurningEntireSupply() (gas: 155858)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 736.58ms (3.04s CPU time)\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131179, ~: 132064)\nLogs:\n  Bound result 815109694875480582739383033\n  Bound result 545289029090742702325542279\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 736.63ms (45.73ms CPU time)\n\nRan 2 tests for test/BurnReferenceInvariant.t.sol:BurnReferenceInvariantTest\n[PASS]\nBurnReferenceInvariantTest invariants:\n[PASS] invariant_anchorStaysInsideTheBandAroundTheLastLiveReference\n[PASS] invariant_burnLedgerIsConserved\n[PASS] invariant_everyUnlockSettled\n[PASS] invariant_referenceStateMatchesTheModel\n BurnReferenceInvariantTest invariants (runs: 160, calls: 7680, reverts: 0)\n\n╭----------------------+-----------+-------+---------+----------╮\n| Contract             | Selector  | Calls | Reverts | Discards |\n+===============================================================+\n| BurnReferenceHandler | accrue    | 1085  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | burn      | 1039  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | movePlain | 1117  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | movePool4 | 1065  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | poke      | 1120  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | roll      | 1127  | 0       | 0        |\n|----------------------+-----------+-------+---------+----------|\n| BurnReferenceHandler | setOracle | 1127  | 0       | 0        |\n╰----------------------+-----------+-------+---------+----------╯\n\nLogs:\n  Bound result 17202\n  Bound result 11737\n  Bound result 10155\n  Bound result 3624\n  Bound result 7726\n  Bound result 361\n  Bound result -11603\n  Bound result 34264337690598449373\n  Bound result 59990000000000025565\n  Bound result 51\n  Bound result 9164337592223260843\n  Bound result -1\n  Bound result 9164337592223260845\n  Bound result 50\n  Bound result 9164337592223260845\n  Bound result 9164337592223260845\n  Bound result 9164337592223260845\n  Bound result 47412099950568636740\n  Bound result 8524\n  Bound result 9164337592223260845\n  Bound result 54472783346831141675\n  Bound result 32976061288194611548\n  Bound result 56232789139719177948\n  Bound result 59990000000000000964\n  Bound result 9164337592223260845\n  Bound result 9164337592223260845\n\n[PASS] test_handlerReachesEveryBurnOutcome() (gas: 5566402)\nLogs:\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 0\n  Bound result 30000000000000000000\n  Bound result 0\n  Bound result 40000000000000000000\n  Bound result 0\n  Bound result 100\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.80s (1.80s CPU time)\n\nRan 2 tests for test/MedallionLifecycleInvariant.t.sol:MedallionLifecycleInvariantTest\n[PASS]\nMedallionLifecycleInvariantTest invariants:\n[PASS] invariant_everyUnlockSettlesAllCurrencyDeltas\n[PASS] invariant_feeClaimsConserveAllFlows\n[PASS] invariant_retirementAndBurnsReachOnlyFixedRecipients\n MedallionLifecycleInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------------+----------------------+-------+---------+----------╮\n| Contract                  | Selector             | Calls | Reverts | Discards |\n+===============================================================================+\n| MedallionLifecycleHandler | attemptRetire        | 2393  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | burn                 | 2345  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | donate               | 2322  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | partialSwap          | 2311  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | poke                 | 2320  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | trade                | 2327  | 0       | 0        |\n|---------------------------+----------------------+-------+---------+----------|\n| MedallionLifecycleHandler | unauthorizedCallback | 2366  | 0       | 0        |\n╰---------------------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 34264337789633652473\n  Bound result 4966\n  Bound result 2264337567134562831\n  Bound result 20672\n  Bound result 18564\n  Bound result 2264337567134562831\n  Bound result 34264337789633652473\n  Bound result 2264337567134562831\n  Bound result 34264337789633652473\n  Bound result 34264337789633652473\n  Bound result 111\n  Bound result 2264337567134562831\n  Bound result 21267\n  Bound result 44130901798\n  Bound result 14625564293\n\n[PASS] test_handlerReachesBurnsRetirementAndAtomicFailures() (gas: 3653828)\nLogs:\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 40000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.26s (3.26s CPU time)\n\nRan 1 test for test/FareTokenProperties.t.sol:FareTokenStatefulPropertiesTest\n[PASS]\nFareTokenStatefulPropertiesTest invariants:\n[PASS] invariant_approvalsAreIsolatedAndOnlySuccessfulSpendingConsumesThem\n[PASS] invariant_everyIssuedUnitIsHeldOrBurned\n FareTokenStatefulPropertiesTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭------------------------+----------+-------+---------+----------╮\n| Contract               | Selector | Calls | Reverts | Discards |\n+================================================================+\n| FareTokenActionHandler | approve  | 4119  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | burn     | 4044  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | spend    | 4079  | 0       | 0        |\n|------------------------+----------+-------+---------+----------|\n| FareTokenActionHandler | transfer | 4142  | 0       | 0        |\n╰------------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.26s (4.26s CPU time)\n\nRan 12 test suites in 4.26s (12.93s CPU time): 151 tests passed, 0 failed, 0 skipped (151 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":84,\"docs/security-review.md\":26,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":460,\"test/BurnGuards.t.sol\":333,\"test/BurnReferenceInvariant.t.sol\":545,\"test/FareToken.t.sol\":131,\"test/FareTokenProperties.t.sol\":268,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/MedallionLifecycleInvariant.t.sol\":395,\"test/PROPERTIES.md\":27,\"test/Pool4Route.t.sol\":383,\"test/RetireBurn.t.sol\":795,\"test/RetirementSecurity.t.sol\":418,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/Pool4HookMock.sol\":71,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"7b970c666387561320525ef5e82a74158d5fe8d819780c7e96f55660151b462d","verifiedTreeHash":"96cd5d6da46b9cf89d67f9ebaf9ca30bd50be196","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":5142,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.93s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:342:53\n    │\n342 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:345:47\n    │\n345 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:348:60\n    │\n348 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:349:17\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:50\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:354:57\n    │\n354 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:55\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:62\n    │\n356 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:35\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:359:42\n    │\n359 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:53\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:60\n    │\n361 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:81\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:364:88\n    │\n364 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:365:17\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:327:9\n    │\n327 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:278:9\n    │\n278 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:153:63\n    │\n153 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:151:22\n    │\n151 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:170:25\n    │\n170 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:27\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:171:34\n    │\n171 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:18\n    │\n172 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:192:46\n    │\n192 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n193 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n194 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:198:13\n    │\n198 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:203:9\n    │\n203 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:204:9\n    │\n204 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:412:9\n    │\n412 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:427:9\n    │\n427 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:234:9\n    │\n234 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:271:53\n    │\n271 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:275:42\n    │\n275 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:276:26\n    │\n276 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:320:25\n    │\n320 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:16\n    │\n321 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:321:23\n    │\n321 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:329:65\n    │\n329 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:349:31\n    │\n349 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:365:38\n    │\n365 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:425:22\n    │\n425 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:445:15\n    │\n445 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:450:39\n    │\n450 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:278:17\n    │\n278 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:522:17\n    │\n522 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:586:21\n    │\n586 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":695,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131464, ~: 132112)\nLogs:\n  Bound result 8134\n  Bound result 80\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 8.13ms (11.85ms CPU time)\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 897292)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 694530)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 418490)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 467614)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 706517)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 749464)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735358)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 714034)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 53.68ms (6.28ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 297201, ~: 301807)\nLogs:\n  Bound result 70997819\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299320)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286248)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253823)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240495)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23011)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352733)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120597)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300691)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286102)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299126)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287188)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518508)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 10245152)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 9883035)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561700)\n[PASS] test_permissionsAndFlags() (gas: 15502)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221526)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631639)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6614334)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 423986)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 116.30ms (91.23ms CPU time)\n\nRan 48 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 912403, ~: 882196)\nLogs:\n  Bound result 998000212145383229\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 523705)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 336751)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 550249)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 278435)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 618389)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 544506)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 521480)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 479757)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 415973)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 922180)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 604433)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 550356)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 455034)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 570685)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 594018)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1135621)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 245619)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32799)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 804443)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 886370)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 41593)\n[PASS] test_constructorSeedsClosedOracle() (gas: 18007403)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1523986)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 598073)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 640546)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 821171)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 19087962)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 18392389)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 17903013)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 474105)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 17911693)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 443120)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 17904389)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302823)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 858489)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324603)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542129)\n[PASS] test_retireBelowCapReverts() (gas: 238779)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678636)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692810)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201558)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247479)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267040)\n[PASS] test_revertingOracleNeverSeeds() (gas: 17902733)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203418)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 17903106)\n[PASS] test_staleBoundaryIsInclusive() (gas: 611852)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 116.46ms (569.62ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 989   | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1059  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 557.12ms (543.30ms CPU time)\n\nRan 5 test suites in 563.18ms (851.69ms CPU time): 90 tests passed, 0 failed, 0 skipped (90 total tests)\n","passed":true},{"durationMs":91,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":82,\"docs/security-review.md\":23,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":455,\"test/FareToken.t.sol\":131,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/RetireBurn.t.sol\":645,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":2765,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/MedallionHook.sol:415: MedallionHook._stepAnchor(int24) (src/MedallionHook.sol#415-428) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MedallionHook.sol:185: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#185-205):\n[medium/medium] uninitialized-local at src/MedallionHook.sol:148: MedallionHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/MedallionHook.sol#148) is a local variable never initialized\n[medium/medium] unused-return at src/MedallionHook.sol:384: MedallionHook._spot(PoolKey) (src/MedallionHook.sol#384-388) ignores return value by (sqrtPrice,tick,None,None) = poolManager.getSlot0(key.toId()) (src/MedallionHook.sol#386)\n[low/medium] events-maths at src/MedallionHook.sol:251: MedallionHook.unlockCallback(bytes) (src/MedallionHook.sol#251-282) should emit an event for: \n[low/medium] reentrancy-benign at src/MedallionHook.sol:251: Reentrancy in MedallionHook.unlockCallback(bytes) (src/MedallionHook.sol#251-282):\n[low/medium] reentrancy-benign at src/MedallionHook.sol:185: Reentrancy in MedallionHook.retire() (src/MedallionHook.sol#185-205):\n[low/medium] reentrancy-events at src/MedallionHook.sol:324: Reentrancy in MedallionHook._collect(uint256) (src/MedallionHook.sol#324-330):","passed":true},{"durationMs":814,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/MedallionHook.sol:192: Reentrancy: State change after external call (2 places)\n[low] large-numeric-literal at src/MedallionHook.sol:28: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/FareToken.sol:26: Literal Instead of Constant (20 places)\n[low] missing-inheritance at src/FareToken.sol:8: Missing Inheritance\n[low] state-change-without-event at src/MedallionHook.sol:251: State Change Without Event\n[low] unchecked-return at src/MedallionHook.sol:202: Unchecked Return (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"bb073f41c049deeacccee49a29d17c3cf0176f8262219ed6b571b0c7d427d683","verifiedTreeHash":"26964f7f2454cef0cb5996cff5d561148b59de7b","verifierVersion":"0.1.0+da6bdbe5"},{"checks":[{"durationMs":3660,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.50s\nCompiler run successful!\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:344:53\n    │\n344 │         if (MEDALLION_NFT.code.length == 0) return (false, address(0), data);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:347:47\n    │\n347 │         if (!ok || data.length != 32) return (false, address(0), data);\n    │                                               ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:350:60\n    │\n350 │         if (word == 0 || word > type(uint160).max) return (false, address(0), data);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:351:17\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:50\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                  ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:356:57\n    │\n356 │         if (POOL4_HOOK.code.length == 0) return (false, false, 0);\n    │                                                         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:55\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                       ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:358:62\n    │\n358 │         if (!okOpen || openData.length != 32) return (false, false, 0);\n    │                                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:35\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                   ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:361:42\n    │\n361 │         if (openWord > 1) return (false, false, 0);\n    │                                          ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:53\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:363:60\n    │\n363 │         if (!okRef || refData.length != 32) return (false, false, 0);\n    │                                                            ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:81\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                 ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:366:88\n    │\n366 │         if (refWord < TickMath.MIN_TICK || refWord > TickMath.MAX_TICK) return (false, false, 0);\n    │                                                                                        ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[boolean-cst]: misuse of a boolean constant\n    ╭▸ src/MedallionHook.sol:367:17\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/boolean-cst\n\nwarning[missing-events-arithmetic]: `totalFees` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:329:9\n    │\n329 │         totalFees = previous + fee;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `totalIMDBurned` is changed without an event but is used in arithmetic\n    ╭▸ src/MedallionHook.sol:280:9\n    │\n280 │         totalIMDBurned += output;\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:155:63\n    │\n155 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee, 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:153:22\n    │\n153 │             _collect(uint128(fee));\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:172:25\n    │\n172 │         uint256 gross = uint256(ethDelta < 0 ? -ethDelta : ethDelta);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:27\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:173:34\n    │\n173 │         int128 feeAfter = int128(int256(gross / 50));\n    │                                  ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:174:18\n    │\n174 │         _collect(uint128(feeAfter));\n    │                  ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `retired` is updated\n    ╭▸ src/MedallionHook.sol:194:46\n    │\n194 │               (bool ok, bytes memory reason) = MEDALLION_NFT.call(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n195 │ ┃                 abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", previousOwner, DEAD, MEDALLION_ID)\n196 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:200:13\n    │\n200 │             emit MedallionRetired(previousOwner);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:205:9\n    │\n205 │         emit CreatorPaid(CREATOR, CREATOR_CAP);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:206:9\n    │\n206 │         emit LastFare(MEDALLION_ID, LAST_FARE_HASH, LAST_FARE);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:417:9\n    │\n417 │         emit AnchorUpdated(anchorTick, lastRefTick, true);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:432:9\n    │\n432 │         emit AnchorUpdated(anchorTick, lastRefTick, false);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:236:9\n    │\n236 │         emit IMDBurned(amount, imdOut, viaPool4);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:273:53\n    │\n273 │                 zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:277:42\n    │\n277 │         if (int256(result.amount0()) != -int256(amount) || result.amount1() <= 0) revert PartialFill();\n    │                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:278:26\n    │\n278 │         uint256 output = uint128(result.amount1());\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:322:25\n    │\n322 │         uint256 gross = uint256(amount < 0 ? -amount : amount);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:16\n    │\n323 │         return int128(int256(gross / 50));\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:323:23\n    │\n323 │         return int128(int256(gross / 50));\n    │                       ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MedallionHook.sol:331:65\n    │\n331 │         if (previous < CREATOR_CAP && totalFees >= CREATOR_CAP) emit Recouped(totalFees, block.number);\n    │                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:351:31\n    │\n351 │         return (true, address(uint160(word)), data);\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:367:38\n    │\n367 │         return (true, openWord == 1, int24(refWord));\n    │                                      ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:430:22\n    │\n430 │         anchorTick = int24(target);\n    │                      ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MedallionHook.sol:450:15\n    │\n450 │             ++length;\n    │               ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MedallionHook.sol:455:39\n    │\n455 │             buffer[--length] = bytes1(uint8(48 + value % 10));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/RetireBurn.t.sol:68:17\n   │\n68 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:280:17\n    │\n280 │         vm.roll(block.number + 4);\n    │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:524:17\n    │\n524 │         vm.roll(block.number + 50_401);\n    │         ────────━━━━━━━━━━━━────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:545:17\n    │\n545 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:636:17\n    │\n636 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:641:21\n    │\n641 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:665:17\n    │\n665 │         vm.roll(block.number + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ────────━━━━━━━━━━━━───────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n674 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS());\n    │         ───────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:667:20\n    │\n667 │         seededAt = block.number;\n    │                    ━━━━━━━━━━━━\n    ‡\n682 │         vm.roll(seededAt + hook.STALE_AFTER_BLOCKS() + 1);\n    │         ───────────────────────────────────────────────── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n    ╭▸ test/RetireBurn.t.sol:736:21\n    │\n736 │             vm.roll(block.number + 1);\n    │             ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockNumber()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":492,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/FareToken.t.sol:FareTokenTest\n[PASS] testBurnAndBurnFromReduceSupply() (gas: 157786)\n[PASS] testFuzzConservationAcrossTransferAndBurn(uint256,uint256) (runs: 256, μ: 131452, ~: 132088)\nLogs:\n  Bound result 48000000000000000\n  Bound result 105151830\n\n[PASS] testInfiniteAllowancePersistsForTransfersAndBurns() (gas: 162962)\n[PASS] testMetadataAndExactInitialSupply() (gas: 57971)\n[PASS] testNoMintOrAdministrativeSelectors() (gas: 149672)\n[PASS] testRejectedOperationsPreserveSupplyBalancesAndAllowance() (gas: 239187)\n[PASS] testRuntimeContainsNoForbiddenOpcodes() (gas: 668468)\n[PASS] testTransferFromConsumesAllowance() (gas: 128954)\n[PASS] testTransferIsExactAndSelfTransferPreservesBalance() (gas: 124478)\n[PASS] testZeroValueOperations() (gas: 135688)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 4.64ms (7.24ms CPU time)\n\nRan 23 tests for test/HookFees.t.sol:HookFeesTest\n[PASS] testFuzz_allFourModesConserveClaims(uint96,uint8) (runs: 256, μ: 297419, ~: 301917)\nLogs:\n  Bound result 15550\n\n[PASS] test_afterSwapBuyPartialFillChargesOnlyActualGrossETH() (gas: 299430)\n[PASS] test_afterSwapSellPartialFillChargesOnlyActualGrossETH() (gas: 286358)\n[PASS] test_beforeSwapBuyPartialFillRevertsAndRollsBackFee() (gas: 253933)\n[PASS] test_beforeSwapSellPartialFillRevertsAndRollsBackFee() (gas: 240605)\n[PASS] test_constructorRejectsIncorrectFlags() (gas: 23131)\n[PASS] test_donatedClaimsDoNotIncreaseFeesOrBurnable() (gas: 352843)\n[PASS] test_enabledCallbacksAndUnlockRejectUnauthorizedCalls() (gas: 120641)\n[PASS] test_exactInputBuyMintsETHClaims() (gas: 300801)\n[PASS] test_exactInputSellChargesTwoPercentOfGrossETH() (gas: 286212)\n[PASS] test_exactOutputBuyChargesTwoPercentOfGrossETH() (gas: 299236)\n[PASS] test_exactOutputSellMintsETHClaims() (gas: 287298)\n[PASS] test_feeRoundingTruncatesAtWeiPrecision() (gas: 518728)\n[PASS] test_freshManagerWithTokenOnlyLiquidityAcceptsBuy() (gas: 12716738)\n[PASS] test_lastFareIsPinned() (gas: 34673)\n[PASS] test_nonNativeInitializationNeverClaimsLaunchPool() (gas: 6023474)\n[PASS] test_otherNativePoolRemainsFeeFree() (gas: 561832)\n[PASS] test_permissionsAndFlags() (gas: 15392)\n[PASS] test_poolDonationsDoNotChargeHookFees() (gas: 221614)\n[PASS] test_recoupedIsEmittedOnceAndOnlyExcessBecomesBurnable() (gas: 631771)\n[PASS] test_runtimeContainsNoForbiddenOpcodes() (gas: 6662380)\n[PASS] test_statusTruncatesAndCapSentenceIsExact() (gas: 424030)\n[PASS] test_swapsDoNotDependOnExternalRecipientsOrMainnetContracts() (gas: 507572)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 25.26ms (59.48ms CPU time)\n\nRan 8 tests for test/HookLifecycle.t.sol:HookLifecycleTest\n[PASS] test_burnCannotNestInsideAnotherManagerUnlock() (gas: 908008)\n[PASS] test_realBurnBeforeRetirementPreservesFullCreatorReserve() (gas: 699832)\n[PASS] test_realEmptyPoolRevertsPartialFillWithoutSpendingClaims() (gas: 423859)\n[PASS] test_realFallbackBurnUsesSmallerBatchAndSettles() (gas: 470096)\n[PASS] test_realPoolCallerMinimumFailureRollsBackSwapAndLedger() (gas: 717255)\n[PASS] test_realRetirementPaysExactlyCapAndPlainBurnSettlesEveryDelta() (gas: 754788)\n[PASS] test_realRetirementRejectingRecipientRollsBackNFTAndClaims() (gas: 735202)\n[PASS] test_retirementCannotNestInsideAnotherManagerUnlock() (gas: 713856)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 52.50ms (6.11ms CPU time)\n\nRan 58 tests for test/RetireBurn.t.sol:RetireBurnTest\n[PASS] testFuzz_burnBatchAndRetirementKeepExactReservation(uint96,bool) (runs: 256, μ: 923011, ~: 950106)\nLogs:\n  Bound result 998000000000000521\n\n[PASS] test_burnBatchUsesRemainingBurnable() (gas: 529294)\n[PASS] test_burnCannotJoinAnotherCallersUnlock() (gas: 342274)\n[PASS] test_burnClosedOracleOnlyPlainFallbackAllowedBeforeBudgetCheck() (gas: 555103)\n[PASS] test_burnDonationsCannotIncreaseBatchOrBudget() (gas: 281005)\n[PASS] test_burnEnforcesCooldownAgainAfterSuccess() (gas: 627979)\n[PASS] test_burnExactlyMinimumWorksBeforeRetirement() (gas: 550029)\n[PASS] test_burnFullInputWithZeroOutputReverts() (gas: 527003)\n[PASS] test_burnGuardIsOneSidedAtHigherSpot() (gas: 485104)\n[PASS] test_burnMissingOracleFallbackWorksAfterPriorSeed() (gas: 418499)\n[PASS] test_burnNonzeroReferenceQuote() (gas: 932896)\n[PASS] test_burnNormalUsesExactFixedPool4KeyAndMaximumBatch() (gas: 609978)\n[PASS] test_burnPlainNormalAllows300TicksButNoMore() (gas: 561006)\n[PASS] test_burnPlainNormalUsesExactFixedKey() (gas: 460358)\n[PASS] test_burnPool4RejectsBelowReferenceBeyond150Ticks() (gas: 581290)\n[PASS] test_burnReentrancyRefusedAcrossAllPermissionlessEntrypoints() (gas: 599453)\n[PASS] test_burnReferenceFloorExcludesLPFeeAndRespectsCallerMinimum() (gas: 1151618)\n[PASS] test_burnReserveUnavailableBelowCapAndBelowMinimum() (gas: 250473)\n[PASS] test_burnTooSoonTakesPrecedence() (gas: 32866)\n[PASS] test_burnZeroAndPartialFillRevertAndRestoreLedger() (gas: 815203)\n[PASS] test_completeLifecyclePreservesReservationAndStatus() (gas: 891782)\n[PASS] test_constructorSeedsAnchorAndStartsCooldown() (gas: 51938)\n[PASS] test_constructorSeedsClosedOracle() (gas: 15157941)\n[PASS] test_fallbackAnchorClampsToLastReferenceBand() (gas: 1522419)\n[PASS] test_fallbackBurnUsesStartOfBlockAnchorAfterPoke() (gas: 600448)\n[PASS] test_fallbackDownwardPokeCannotRelaxSameBlockGuard() (gas: 645337)\n[PASS] test_fallbackTolerance150AndOneStepPerBlockEvenAfterIdle() (gas: 825814)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackMinimumOutput() (gas: 16233187)\n[PASS] test_firstDelayedSeedProtectsSameBlockFallbackPriceGuard() (gas: 15535043)\n[PASS] test_invalidOracleBoolNeverSeeds() (gas: 15023307)\n[PASS] test_livePokeDoesNotSpendFeesOrResetBurnCooldown() (gas: 882518)\n[PASS] test_liveSeedFreshnessBoundaryIsInclusive() (gas: 723560)\n[PASS] test_liveSeedFreshnessExpiresBackToFallback() (gas: 770922)\n[PASS] test_malformedOracleFallsBackAfterSeed() (gas: 476565)\n[PASS] test_neverReadOracleDisablesBurnAndPoke() (gas: 15034249)\n[PASS] test_noPriorBurnAllowsNormalAfterLongIdle() (gas: 448489)\n[PASS] test_outOfRangeOracleTickNeverSeeds() (gas: 15024639)\n[PASS] test_pokeNormalReseedsReferencePermissionlessly() (gas: 302740)\n[PASS] test_priorBurnBecomesStaleAndFallbackResumesNormalLater() (gas: 872205)\n[PASS] test_retireAlreadyDeadNeedsNoApprovalAndEmitsNoTransferEvent() (gas: 324508)\n[PASS] test_retireAtomicTransferExactPaymentAndLastFareEvents() (gas: 542105)\n[PASS] test_retireBelowCapReverts() (gas: 238844)\n[PASS] test_retireCreatorRefusalRollsBackNFTAndClaims() (gas: 678569)\n[PASS] test_retireNFTAndCreatorReentrancyBothRefused() (gas: 692521)\n[PASS] test_retireNoNFTCodeAndMalformedOwnerFailClosed() (gas: 201380)\n[PASS] test_retireSuccessfulCallWithoutMovingNFTReverts() (gas: 247412)\n[PASS] test_retireWithoutApprovalLeavesEverythingUnchanged() (gas: 267105)\n[PASS] test_revertingOracleNeverSeeds() (gas: 15022961)\n[PASS] test_sepoliaRetirementAndBurnRevertEvenWithEtchedMainnetMocks() (gas: 203396)\n[PASS] test_shortOracleReturnNeverSeeds() (gas: 15023334)\n[PASS] test_staleBoundaryIsInclusive() (gas: 621418)\n[PASS] test_staleClosedOracleFallbackPokesCannotRefreshFreshness() (gas: 1166682)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeDownwardMove() (gas: 1082316)\n[PASS] test_staleLivePokeRecoversPlainAfterLargeUpwardMove() (gas: 1081240)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeDownwardMove() (gas: 1077824)\n[PASS] test_staleLivePokeRecoversPool4AfterLargeUpwardMove() (gas: 1076812)\n[PASS] test_staleLivePokeRecoversWithoutInitializedPlainPool() (gas: 9670773)\n[PASS] test_staleMalformedOracleFallbackPokesCannotRefreshFreshness() (gas: 1146915)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 59.65ms (351.64ms CPU time)\n\nRan 1 test for test/HookAccountingInvariant.t.sol:HookAccountingInvariantTest\n[PASS]\nHookAccountingInvariantTest invariants:\n[PASS] invariant_claimsCoverLedgerAndIncludeDonationsExactly\n[PASS] invariant_onlyFeesAccrueAndCreatorDoesNotReceiveSwapPayments\n HookAccountingInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| FeeActionHandler | donateClaims | 991   | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| FeeActionHandler | trade        | 1057  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 394.32ms (383.89ms CPU time)\n\nRan 5 test suites in 398.08ms (536.37ms CPU time): 100 tests passed, 0 failed, 0 skipped (100 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FareToken.approve(address,uint256)\",\"FareToken.burn(uint256)\",\"FareToken.burnFrom(address,uint256)\",\"FareToken.transfer(address,uint256)\",\"FareToken.transferFrom(address,address,uint256)\",\"MedallionHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"MedallionHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MedallionHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MedallionHook.burnIMD(bool,uint256)\",\"MedallionHook.pokeAnchor()\",\"MedallionHook.retire()\",\"MedallionHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":29,\"docs/dependencies.md\":18,\"docs/operations.md\":84,\"docs/security-review.md\":26,\"foundry.toml\":22,\"launch.json\":27,\"remappings.txt\":4,\"src/FareToken.sol\":91,\"src/MedallionHook.sol\":460,\"test/FareToken.t.sol\":131,\"test/HookAccountingInvariant.t.sol\":95,\"test/HookFees.t.sol\":336,\"test/HookLifecycle.t.sol\":257,\"test/RetireBurn.t.sol\":795,\"test/helpers/FareDeploy.sol\":28,\"test/helpers/FareRouter.sol\":95,\"test/mocks/RetireBurnMocks.sol\":247},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e686e9ac1998d382ab2ade7d80dd80d20dd28da3ab1abc06a4ba136c22262bff","verifiedTreeHash":"83548986570f7b83955995c2900f0ba91ee25f59","verifierVersion":"0.1.0+da6bdbe5"}]}