{"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":"983046c0-f83b-4c29-ab1e-6e631b668d83","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"e419c0e1693bc06054fe01a05789f21eca270094634d688d15ace59eed5f97a6","dependsOn":["manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"f924a93fc4f015d0b1857e4e340dc7e33e07ac270647f999774df5469d1afc20","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"}],"objective":"Begin with an audit-imported-code step: audit both contracts, the library and the deploy script exactly as they arrived in this repository, before anything is adapted, and report findings rather than fixing them. Only then deploy the Ponzinomics ($PIMD) contracts to Robinhood Chain (4663) and open the pool, exactly as they are. Two contracts to deploy. PimdToken is an ERC-20 named Ponzinomics, symbol PIMD, with a fixed total supply of exactly 1,000,000,000 at 18 decimals, minted once in a constructor that takes no arguments, to whoever deploys it; there is no owner, no mint, no burn and no pause, and burning is a plain transfer to 0xdEaD so totalSupply never moves. PimdHook is a Uniswap V4 hook whose constructor takes the pool manager, the token address and the launch factory, so its constructorArgs are $poolManager, $token and $factory; everything else is a source constant. It does not gate the opening price: it accepts the pool the factory opens and reads the tick only for its Launched event. It taxes every trade in IMD, 2.4% on buys and 5.6% on sells, split 75% to holders and 25% to the team, taking its fees as ERC-6909 claims on the quote currency. The pool's own 1.25% fee sits on top of that tax and the hook never touches it. A third contract, PimdEngine, is already deployed at 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4 and its address is baked into PimdHook as a constant: audit it as imported code, but do not deploy or redeploy it. Do not change the economics, the tax rates, the split, the tier ladder or the opening tick: they are settled, and the opening tick follows the chain policy's 2500 IMD initial market cap. If you find a defect, report it rather than redesigning around it. Three things deserve the most attention. First, the pool is only correct if IMD sorts below PIMD as currency0: beforeInitialize refuses the other ordering, refuses a second pool, refuses a wrong quote currency and refuses an opening tick more than 300 from 129000, and it is the single gate on all of it. Second, beforeAddLiquidity must let the factory's seed through exactly once and refuse every add after it, because 'third-party liquidity is blocked' is an advertised property; and beforeRemoveLiquidity must refuse every negative liquidity delta for ever while still allowing the zero delta that collects the pool's own fee. Third, the engine must never read holder weights while the PoolManager is unlocked, which is where a flash borrower would stand.","parentJobId":null,"planHash":"665dccc90ab8a85279df3bb7957587818ec475ad189e6a42092234d49b8a58c1","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"983046c0-f83b-4c29-ab1e-6e631b668d83","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1414-src-pimd-pimdtoken-sol-src-pimd-pimdhook"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51163","feedbackHash":"272d7f5144cd7d3e69aed06305d8829e9f2a9bd462e7204820546a591601b17d","nodeKey":"audit_imported_code","submissionHash":"e419c0e1693bc06054fe01a05789f21eca270094634d688d15ace59eed5f97a6","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51330","feedbackHash":"80e5125e4a323c245c21abc9ef84fcef79e154aa41705781b41fb9b402152157","nodeKey":"manifest","submissionHash":"f924a93fc4f015d0b1857e4e340dc7e33e07ac270647f999774df5469d1afc20","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"99f58cd7caaa85294e8456deaf71c63e0c08ed8a1897899116747c930a4d0dc4","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8af9903f4ad1eed0","findings":[{"citation":"resolved","description":"The brief, README.md:51-54 ('refuses ... an opening tick more than 300 from LAUNCH_TICK') and the contract's own header at PimdHook.sol:42-43 ('it checks the caller, the pairing, the quote currency, the fee tier and the opening price') all advertise an opening-price gate. The code computes the tick only for the Launched event (lines 262-270 say so explicitly; commit 63d1f82 removed the check), so LAUNCH_TICK and LAUNCH_TICK_TOLERANCE are decorative and the factory can open the one and only PIMD pool at any sqrtPriceX96 TickMath accepts. The brief itself is self-contradictory on this point (it says both 'does not gate the opening price' and 'refuses an opening tick more than 300 from 129000'); whichever reading the author settles on, the three documents and the code must agree, and today the advertised guarantee is false. Because beforeInitialize is the single gate and the hook launches exactly once, a wrong price is permanent: there is no second pool and liquidity can never be moved. Reported, not redesigned: restoring the gate is a one-line check, dropping it means correcting README.md:54, PimdHook.sol:43 and the brief.","line":270,"path":"src/pimd/PimdHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"solmate/src/tokens/ERC20.sol\";\nimport {PimdToken} from \"src/pimd/PimdToken.sol\";\nimport {PimdHook} from \"src/pimd/PimdHook.sol\";\n\ncontract QuoteStub is ERC20(\"Identity.md\", \"IMD\", 18) {}\n\n/// Points the quote at a local stand-in and changes nothing else; every line of beforeInitialize executed\n/// is the production contract's.\ncontract HookUnderTest is PimdHook {\n    address private immutable _quote;\n    constructor(IPoolManager pm, address token_, address factory_, address quote_) PimdHook(pm, token_, factory_) {\n        _quote = quote_;\n    }\n    function quoteToken() public view override returns (address) { return _quote; }\n}\n\ncontract FactoryStub {\n    IPoolManager immutable m;\n    constructor(IPoolManager m_) { m = m_; }\n    function open(PoolKey memory key, uint160 p) external { m.initialize(key, p); }\n}\n\n/// README (\"refuses ... an opening tick more than 300 from LAUNCH_TICK\"), the hook's own NatSpec at\n/// PimdHook.sol:42-43 and the launch brief all say beforeInitialize refuses an opening price more than\n/// LAUNCH_TICK_TOLERANCE from LAUNCH_TICK. The code reads the tick for the event and gates nothing, so the\n/// factory can open this pool at any price at all. The price used here is launch.json's own\n/// pool.initialPrice, 2^96, which is tick 0: one IMD per PIMD, a 1,000,000,000 IMD opening cap against the\n/// policy's 2,500.\ncontract TickGateTest is Test {\n    uint160 constant FLAGS = uint160(\n        Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG\n            | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG\n            | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG\n    );\n\n    IPoolManager manager;\n    QuoteStub imd;\n    PimdToken token;\n    FactoryStub factory;\n    PimdHook hook;\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        imd = new QuoteStub();\n        // mine a token address above the quote so IMD is currency0, as the hook requires\n        bytes32 initHash = keccak256(type(PimdToken).creationCode);\n        for (uint256 i; i < 100_000; ++i) {\n            address predicted = vm.computeCreate2Address(bytes32(i), initHash, address(this));\n            if (uint160(predicted) > uint160(address(imd))) {\n                token = new PimdToken{salt: bytes32(i)}();\n                break;\n            }\n        }\n        require(address(token) != address(0), \"no salt\");\n        factory = new FactoryStub(manager);\n        // mine a hook address whose low bits are exactly the permission flags\n        bytes32 hookInit = keccak256(\n            abi.encodePacked(type(HookUnderTest).creationCode, abi.encode(manager, address(token), address(factory), address(imd)))\n        );\n        for (uint256 i; i < 500_000; ++i) {\n            address predicted = vm.computeCreate2Address(bytes32(i), hookInit, address(this));\n            if (uint160(predicted) & Hooks.ALL_HOOK_MASK == FLAGS) {\n                hook = PimdHook(payable(address(new HookUnderTest{salt: bytes32(i)}(manager, address(token), address(factory), address(imd)))));\n                require(address(hook) == predicted, \"hook addr\");\n                break;\n            }\n        }\n        require(address(hook) != address(0), \"no hook salt\");\n    }\n\n    function _key() internal view returns (PoolKey memory) {\n        return PoolKey({\n            currency0: Currency.wrap(address(imd)),\n            currency1: Currency.wrap(address(token)),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n    }\n\n    /// launch.json's initialPrice, 79228162514264337593543950336, is tick 0: 129,000 ticks from LAUNCH_TICK.\n    /// The advertised gate refuses it. The code opens the pool.\n    function test_the_advertised_tick_gate_refuses_launch_json_initial_price() public {\n        uint160 manifestPrice = 79228162514264337593543950336;\n        assertEq(TickMath.getTickAtSqrtPrice(manifestPrice), 0);\n        vm.expectRevert();\n        factory.open(_key(), manifestPrice);\n        assertFalse(hook.launched(), \"pool opened 129000 ticks away from LAUNCH_TICK\");\n    }\n\n    /// One tick past the tolerance on either side is refused by the advertised gate.\n    function test_the_advertised_tick_gate_refuses_one_tick_past_tolerance() public {\n        uint160 oneTickPast = TickMath.getSqrtPriceAtTick(hook.LAUNCH_TICK() + hook.LAUNCH_TICK_TOLERANCE() + 1);\n        vm.expectRevert();\n        factory.open(_key(), oneTickPast);\n        assertFalse(hook.launched());\n    }\n\n    /// Control: the briefed tick is accepted, so a fix that gates the price does not refuse the real launch.\n    function test_the_briefed_tick_is_accepted() public {\n        factory.open(_key(), TickMath.getSqrtPriceAtTick(hook.LAUNCH_TICK()));\n        assertTrue(hook.launched());\n    }\n}","reproduction":"Factory (the address passed as $factory) calls PoolManager.initialize(key{currency0: IMD, currency1: PIMD, fee: 12500, tickSpacing: 60, hooks: PimdHook}, sqrtPriceX96 = 79228162514264337593543950336). That value is launch.json's own pool.initialPrice and is tick 0: one IMD per PIMD, a 1,000,000,000 IMD opening cap against the policy's 2,500. Expected per README/NatSpec/brief: revert (tick 0 is 129,000 ticks from LAUNCH_TICK). Actual: beforeInitialize returns its selector, launched becomes true, Launched(id, 0) is emitted and the pool is open at that price for ever. The same holds one tick past the tolerance (sqrtPrice at tick 129301). Proof: test/scratch/TickGate.t.sol, two tests fail on this code ('next call did not revert as expected') and the control test that opens at tick 129000 passes.","severity":"medium","snippet":"        int24 tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);","title":"beforeInitialize accepts any opening price; README, the hook's own NatSpec and the brief say it refuses a tick more than 300 from 129000"},{"citation":"resolved","description":"79228162514264337593543950336 is exactly 2^96, the sqrtPriceX96 of tick 0, i.e. a price of 1 PIMD per IMD: a 1,000,000,000 IMD opening market cap on the fixed billion supply, 400,000x the 2,500 IMD the chain policy and the brief specify. The sqrtPriceX96 that tick 129000 implies (IMD as currency0, PIMD as currency1, 2.5e-6 IMD per PIMD) is 50111677533496076234078224273595 (TickMath.getSqrtPriceAtTick(129000); the +/-300 tolerance spans 49365648873540214315780664062218 to 50868980404855525156974128955070). The manifest's notes say the factory derives the real price from policy and treats this field as a 'reference value', but the manifest is the only place the opening price is written down in this tree, nothing a worker runs validates it, and since the hook no longer gates the price (finding 1) a factory that honours the manifest field opens the pool at tick 0 with no on-chain refusal. Every other manifest field checks out: kind, constructorArgs ($poolManager,$token,$factory in declaration order), the eight permissions match getHookPermissions exactly, pairedCurrency is lowercase IMD, fee 12500 is accepted by the hook, tickSpacing 60, token name/symbol/decimals match the constructor, notes are 3,966 characters.","line":31,"path":"launch.json","reproduction":"Read launch.json line 31 and evaluate TickMath.getTickAtSqrtPrice(79228162514264337593543950336): result 0. Expected for a 2,500 IMD opening cap: tick 129000, sqrtPriceX96 50111677533496076234078224273595. If the factory initializes with the manifest value, PimdHook.beforeInitialize accepts it (see finding 1's proof, test_the_advertised_tick_gate_refuses_launch_json_initial_price, which opens the pool at exactly this value) and emits Launched(id, 0).","severity":"medium","snippet":"    \"initialPrice\": \"79228162514264337593543950336\"","title":"launch.json pool.initialPrice encodes tick 0 (1 IMD per PIMD), not the briefed tick 129000, and nothing on chain refuses it"},{"citation":"resolved","description":"The launch rules list 'anything configured after deployment rather than in a constructor' as a medium finding so the adapter can see it. bind is exactly that: a one-shot initializer that sets token, hook, the exclusion list and lastFire after deployment, callable only by the immutable binder. On the live engine at 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4 (reached on Robinhood Chain, chain id 4663) binder() == team() == 0x0960E8Bd80462e3842Bb6620c7C5289A44c4559B, an address with no code, and bound() == false. The hook does not wait for the engine to be bound: afterSwap books 75% of every tax to holdersOwed, and flush()/flushHolders() are permissionless and take real IMD to engine() regardless of the engine's state. Once IMD is inside an unbound engine no function can move it: fire() reverts NotBound, pay/tally need an epoch, there is no sweep, and seed() only adds. So the entire holders' stream rests on one externally-owned key making one call after the launch lands, and bind also cannot be called before the pool is initialized (it reads hook.quote(), which is zero until beforeInitialize runs), so the window where tax can accrue into a dead engine is unavoidable. This is a design decision of imported code and is not redeployed here; it is reported so the adapter and the owner of the binder key plan for it (bind in the same block as the first trade, confirm the key is held, and pass the airdrop distributor in alsoExclude as the script's last console line insists).","line":278,"path":"src/pimd/PimdEngine.sol","reproduction":"State: engine unbound (as it is on chain today), pool launched. Sequence: (1) alice buys 100 IMD of PIMD -> hook.holdersOwed = 1.8 IMD (75% of 2.4%). (2) anyone calls hook.flushHolders() -> poolManager.take(IMD, engine, 1.8e18); imd.balanceOf(engine) == 1.8e18. (3) anyone calls engine.fire() -> revert NotBound(). (4) engine has no other function that transfers IMD out; the 1.8 IMD (and everything that follows it) stays in the contract until, and unless, 0x0960...559B calls bind(token, hook, [...]) with the hook's token and hook addresses. If that key is lost the holders' 75% of all tax is permanently stuck while the team's 25% continues to be paid.","severity":"medium","snippet":"    function bind(address token_, address hook_, address[] calldata alsoExclude) external {","title":"PimdEngine.bind is post-deployment configuration gated to a single EOA; until it runs, every IMD the hook pushes to the engine is unrecoverable"},{"citation":"resolved","description":"Every fee calculation assumes IMD (0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127) is currency0 and PIMD currency1, and beforeInitialize correctly refuses the other ordering, so the gate itself holds. But V4 orders currencies by address, PimdToken has no constructor argument and is deployed by the launch factory, and README.md:51-52 ('The deploy mines a CREATE2 salt so the token's address lands above IMD's') describes machinery that does not exist in this tree: the only salt mining is in test/pimd/PimdBase.t.sol, which is test scaffolding. If the factory's deployment lands PIMD at any address numerically below 0x5F7B..., the probability of which for a CREATE or unmined CREATE2 address is 0x5F7B/0x10000 ~ 37%, then every PoolKey containing PIMD and IMD has PIMD as currency0, beforeInitialize reverts PoolMustPairToken on all of them, and because the token and hook addresses are fixed the launch can never open a pool: the paid deployment is dead. Not a code defect in the hook (it fails closed, as the brief requires) but a launch-viability condition that the adapter must either confirm the factory handles (mines or orders the token address above IMD) or get a decision on; the economics forbid reversing the ordering in the hook.","line":257,"path":"src/pimd/PimdHook.sol","reproduction":"Deploy PimdToken at an address < 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 (e.g. any CREATE2 salt whose predicted address starts 0x00..0x5E). Build the only valid PoolKey: currency0 = PIMD, currency1 = IMD, fee 12500, tickSpacing 60, hooks = PimdHook. Factory calls PoolManager.initialize(key, sqrtPrice). Expected by the launch: pool opens. Actual: PimdHook.beforeInitialize line 257 reverts PoolMustPairToken (c1 == IMD != token), wrapped by V4 as Hooks.WrappedError; swapping the order is rejected by the PoolManager (CurrenciesOutOfOrderOrEqual). No PoolKey exists that this hook will accept, and launched stays false for ever.","severity":"medium","snippet":"        if (c1 != address(token)) revert PoolMustPairToken();","title":"Currency ordering depends on the factory's token address; this repo mines no salt, so about 37% of possible PIMD addresses make the launch permanently unopenable"},{"citation":"resolved","description":"The brief fixes the pool's own fee at 1.25% (12500) 'on top of' the hook tax and launch.json selects 12500 with tickSpacing 60. beforeInitialize additionally accepts 500, 3000 and 10000, and does not look at key.tickSpacing at all (1 through 32767 are all accepted). Since this is the one and only gate and the hook launches exactly once, a factory call with any of those values opens the permanent pool with economics the brief did not approve (a 0.05% LP fee changes the all-in cost from 3.65%/6.85% to 2.45%/5.65%, and a tickSpacing of 32767 changes how the factory's single-sided seed can be ranged). Only the factory can trigger it, so the impact is bounded to a misconfigured launch rather than an attack, hence low. Fee 0 and the dynamic-fee flag are refused, so the 'refuses every listed fee' blocking condition does not apply.","line":247,"path":"src/pimd/PimdHook.sol","reproduction":"Factory calls PoolManager.initialize(key{IMD, PIMD, fee: 500, tickSpacing: 32767, hooks: PimdHook}, sqrtPriceAtTick(129000)). Expected per the brief's settled economics: revert UnsupportedFeeTier(500). Actual: accepted, launched == true, the pool charges 0.05% LP fee for ever. Same for fee 3000 and 10000.","severity":"low","snippet":"        if (\n            key.fee != FEE_TIER_LAUNCH && key.fee != FEE_TIER_LOW && key.fee != FEE_TIER_MID\n                && key.fee != FEE_TIER_HIGH\n        ) revert UnsupportedFeeTier(key.fee);","title":"The single gate admits three fee tiers the brief does not describe and leaves tickSpacing unconstrained"},{"citation":"resolved","description":"The verifier that runs this tree has no network. PimdForkTest.setUp calls vm.createSelectFork with no guard, so a plain `forge test` in that environment reports six failures from this file regardless of the contracts' correctness, and a reviewer reading the summary cannot tell a broken fork endpoint from a broken contract. The 86 local tests all pass (measured: 6 suites, 86 passed, 0 failed with the fork file excluded). Reported so the adapter either gates the fork suite on an env flag / vm.skip or documents the exact exclusion.","line":70,"path":"test/pimd/PimdFork.t.sol","reproduction":"Run `FORK_RPC=http://127.0.0.1:9 forge test --match-path test/pimd/PimdFork.t.sol` (any unreachable endpoint stands in for 'no network'). Expected of a suite meant for offline verification: tests skipped or passing. Actual: `[FAIL: vm.createSelectFork: could not instantiate forked environment ... Connection refused] setUp() (gas: 0)`, 0 passed, 1 failed, and the whole `forge test` run exits non-zero.","severity":"low","snippet":"        vm.createSelectFork(vm.envOr(\"FORK_RPC\", vm.rpcUrl(\"robinhood\")));","title":"PimdFork.t.sol forks unconditionally in setUp, so the test suite fails wherever there is no network"},{"citation":"resolved","description":"The launch is two contracts and the engine is already live and byte-for-byte the local build (see the scope note). This script's only effect if run again is a new PimdEngine whose address is not the one baked into PimdHook.ENGINE_ADDRESS, and the script itself records that this has already happened twice. Its guard against a third drift is opt-in (EXPECT_ENGINE defaults to zero, which disables the check), and DRIP_BPS, MIN_INTERVAL, MIN_BALANCE, FIRE_TIP, TIP_PER_HOLDER, MAX_CATCHUP, MAX_HOLDERS, TEAM_MULTISIG, BINDER, KEEPER_A/B, POOL_MANAGER and IMD can all be replaced from the environment with only range checks in the constructor. None of this affects the deployed engine; it is reported so the adapter does not include this script in the launch path and so the manifest step treats PimdEngine as a dependency, not a target.","line":97,"path":"script/DeployPimd.s.sol","reproduction":"`PRIVATE_KEY=<any funded key> forge script script/DeployPimd.s.sol:DeployPimd --rpc-url robinhood --broadcast` with EXPECT_ENGINE unset: the require at line 98 is satisfied by expected == address(0), a third engine is created at a new address, and PimdHook.engine() still returns 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4, so nothing pays the new one and nothing notices until bind. With `DRIP_BPS=5000 MIN_BALANCE=1` the same command deploys an engine releasing 50% of the pot per 15 minutes to anyone holding 1 wei; the constructor accepts both.","severity":"low","snippet":"        address expected = vm.envOr(\"EXPECT_ENGINE\", address(0));","title":"DeployPimd.s.sol deploys a fresh engine the hook does not name, with every economic parameter overridable from the environment and the address assertion optional"},{"citation":"resolved","description":"Read in full, unmodified, at commit 1e61975: src/pimd/PimdToken.sol (49 lines), src/pimd/PimdHook.sol (553), src/pimd/PimdEngine.sol (880), src/libraries/L2Block.sol (21), script/DeployPimd.s.sol (118), launch.json, README.md, test/pimd/PimdBase.t.sol, and the vendored v4-core Hooks/Lock/PoolManager paths the hook depends on. Skimmed: script/Rehearse*.sol and script/PimdRehearsal.sol (rehearsal only; RehearsePimd.s.sol builds its PoolKey with fee 0 and so does not address the 12500 pool), the stale .audit/ notes from e9d2da4. Reached on chain (Robinhood Chain, chain id 4663, via publicnode/ordofi/robinhood RPCs): PimdEngine at 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4 has 14,619 bytes of runtime code that is byte-identical to the local forge build outside the 960 immutable bytes; its immutables read imd=0x5F7B...7127, poolManager=0x8366...0951, team=binder=0x0960...559B, dripBpsPerPeriod=150, minInterval=900, minBalance=1e24, fireTip=5e16, tipPerHolder=3e15, maxCatchup=21600, maxHolders=700, keepers 0x244F...172D and 0x25Bf...5B98 both true; bound=false, pot=0, holderCount=0, phase Idle. IMD (18 decimals, symbol IMD) and the PoolManager have code. ArbGasInfo reports maxTxGasLimit 32,000,000; a worst-case whole-set tally of 700 holders measured 25,938,000 gas (37,054 per holder) and pay(700) 23,818,306, so the bound holds. Not reachable: the launch factory ($factory is resolved by the pad and no address is in the tree), so whether it calls initialize and modifyLiquidity from its own address, seeds in exactly one add, and does not swap in the initialization L2 block (beforeSwap reverts InitBlockSwap in that block) could not be confirmed; all three are preconditions of this hook and the launch fails closed if any is false. Verified by trace: beforeInitialize is PoolManager-only, factory-only, once-only, refuses PIMD as currency0, refuses any quote but IMD, refuses fee 0 and the dynamic flag; beforeAddLiquidity admits the factory exactly once (positive deltas only reach it); beforeRemoveLiquidity refuses every negative delta from every caller for ever and admits the zero delta that collects LP fees; all four swap shapes charge 2.4%/5.6% of the gross IMD leg, mint equals split, and the PartialFill guard refuses an under-filled specified-IMD swap; fire/tally/pay/register/prune all read the PoolManager's Lock slot (identical to v4-core Lock.sol) before touching any balance; tally weighs on min(balance, lastBal) in one call; the tier ladder is 0/0.5/1/1.5/2/3x; PimdToken is solmate ERC20 with a no-argument constructor minting 1e27 once to msg.sender, no owner/mint/burn/pause, and no DELEGATECALL, SELFDESTRUCT, proxy or initializer anywhere in src/.","line":97,"path":"src/pimd/PimdHook.sol","reproduction":"Not a defect. Reachability evidence: `cast call 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4 'bound()(bool)' --rpc-url https://robinhood-rpc.publicnode.com` returns false; `cast code` of the same address compared against out/PimdEngine.sol/PimdEngine.json deployedBytecode with immutableReferences masked differs in 0 bytes.","severity":"info","snippet":"    address internal constant ENGINE_ADDRESS = 0xFFDB5Dd341F692a29b5a0d3D1577DF683C1bACa4;","title":"Audit scope and reachability: what was read, what was reached on chain, what the hook's three advertised gates do"}],"hash":"e419c0e1693bc06054fe01a05789f21eca270094634d688d15ace59eed5f97a6","nodeId":"1323aaba-9eb4-4272-9c17-7c73fd4e99ee","outcome":"completed","summary":"The review is complete. The source tree is unchanged, the findings file is in place with its proof verified in both directions, and nothing further is needed.\n\n## Outcome\n\nI audited the three contracts, the library, the deploy script and the manifest as they arrived, and wrote eight findings plus a 25-row coverage record to `.imd-findings.json`. No file in `src/`, `script/`, `test/pimd/` or the configuration was modified. The one proof test lives in `test/scratch/TickGate.t.sol`. It fails on the current code and passes when the price gate is restored, which I confirmed by patching and reverting the hook.\n\n**What was read and reached.** All of PimdToken, PimdHook, PimdEngine, L2Block, DeployPimd and launch.json were read line by line. The live engine on Robinhood Chain was reached. Its runtime bytecode is byte-identical to the local build outside the immutables, it is unbound, and it carries the production parameters the script describes. The launch factory could not be reached, since the tree holds no address for it. Its behaviour is a precondition the hook fails closed on.\n\n**Findings, most severe first.**\n\n- **Medium, with proof.** The hook does not gate the opening price. README, the hook's own header and the brief all say it refuses a tick more than 300 from 129000. The code reads the tick for the event only. The brief contradicts itself here, so this is a decision for the author, but the documents and code must agree.\n- **Medium.** The manifest's initialPrice is 2^96, which is tick 0, or one IMD per PIMD. The briefed tick 129000 implies a sqrtPriceX96 of 50111677533496076234078224273595. With the gate absent, nothing on chain refuses the manifest value.\n- **Medium.** The engine's bind call is post-deployment configuration gated to a single externally-owned key. Until it runs, every IMD the hook pushes to the engine is unrecoverable, and tax can accrue before bind is even possible.\n- **Medium.** The currency ordering depends on where the factory lands the token. This repo mines no salt despite README saying it does. Roughly 37% of possible token addresses make every pool key unopenable.\n- **Low.** The gate admits fee tiers 500, 3000 and 10000 beyond the briefed 12500, and ignores tickSpacing.\n- **Low.** The fork test suite forks unconditionally in setUp, so a run with no network fails.\n- **Low.** The deploy script would create a third engine the hook does not name. Its address assertion is optional and its economics are environment-overridable.\n- **Info.** Scope, reachability and the verified-sound list, including the three gates the brief singled out.\n\n**What held under trace.** The one-seed-then-locked liquidity path, the permanent refusal of negative deltas with zero-delta fee collection allowed, all four swap tax shapes, the PartialFill guard, the lock check before every balance read in the engine, and the holder bound. A worst-case 700-holder tally measured 25.9M gas against the chain's confirmed 32M per-transaction limit. All 86 local tests pass.","treeHash":null,"usage":{"cachedInputTokens":1833949,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":63526,"runtime":"claude","turns":38,"wallClockMs":1409066}},{"artifacts":[],"attempt":1,"bundleHash":"ce02b7a03e844c6c5c4aae6c3aa1230b6d3023ab6076be1ac2172935cfde7db4","device":"c5099ef07f22abee","findings":[],"hash":"f924a93fc4f015d0b1857e4e340dc7e33e07ac270647f999774df5469d1afc20","nodeId":"51ba9d88-0775-43b0-93c7-5f344d54c63a","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/983046c0-f83b-4c29-ab1e-6e631b668d83/51ba9d88-0775-43b0-93c7-5f344d54c63a/launch.json) with matching permissions, required pool settings, and audit findings—including the conflicting tick requirements.\n\n`forge build` passed; all 92 tests passed. Notes are within the 4,000-character limit. Only the manifest is submitted; no live deployment was performed.","treeHash":"09f3974f686acc48225e56ec6cbd927ed3fa2e3e","usage":{"cachedInputTokens":2291712,"inputTokens":99821,"model":"gpt-6-astra","outputTokens":12906,"runtime":"codex","turns":11,"wallClockMs":739208}}],"verification":[{"checks":[{"durationMs":65528,"exitCode":0,"name":"build","output":"Compiling 90 files with Solc 0.8.26\nSolc 0.8.26 finished in 65.37s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/pimd/PimdFixes.t.sol:684:5:\n    |\n684 |     function _maxHolders() internal view override returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> test/pimd/PimdFixes.t.sol:939:5:\n    |\n939 |     function _maxHolders() internal view override returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n   ╭▸ src/libraries/L2Block.sol:17:39\n   │\n17 │         (bool ok, bytes memory ret) = ARBSYS.staticcall{gas: ARBSYS_GAS}(abi.encodeWithSignature(\"arbBlockNumber()\"));\n   │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[missing-events-arithmetic]: `totalWeight` is changed without an event but is used in arithmetic\n    ╭▸ src/pimd/PimdEngine.sol:541:9\n    │\n541 │         totalWeight = tw;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `cursor` is changed without an event but is used in arithmetic\n    ╭▸ src/pimd/PimdEngine.sol:542:9\n    │\n542 │         cursor = end;\n    │         ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `tipBudget` is changed without an event but is used in arithmetic\n    ╭▸ src/pimd/PimdEngine.sol:767:9\n    │\n767 │         tipBudget = budget - amt;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `pot` is changed without an event but is used in arithmetic\n    ╭▸ src/pimd/PimdEngine.sol:768:9\n    │\n768 │         pot -= amt;\n    │         ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:264:45\n    │\n264 │             if (c.keepers[i] == address(0)) revert BadConfig();\n    │                                             ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/pimd/PimdHook.sol:278:9\n    │\n278 │         emit Launched(key.toId(), tick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdHook.sol:276:21\n    │\n276 │         initBlock = uint64(L2Block.number());\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/pimd/PimdHook.sol:277:23\n    │\n277 │         launchStart = uint64(block.timestamp);\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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 `locked` is updated\n    ╭▸ src/pimd/PimdEngine.sol:329:17\n    │\n329 │         require(imd.transferFrom(msg.sender, address(this), amount));\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/pimd/PimdEngine.sol:332:9\n    │\n332 │         emit Seeded(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/pimd/PimdHook.sol:306:28\n    │\n306 │         _tstore(_FEE_SLOT, fee);\n    │                            ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdHook.sol:298:46\n    │\n298 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : 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/pimd/PimdHook.sol:298:81\n    │\n298 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : 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/pimd/PimdHook.sol:307:63\n    │\n307 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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/pimd/PimdHook.sol:307:70\n    │\n307 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\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[calls-loop]: external call inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:350:27\n    │\n350 │             uint256 bal = IERC20Min(token).balanceOf(a);\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:807:17\n    │\n807 │             if (IERC20Min(token).balanceOf(a) < minBalance || _shapeChanged(a, _holder[a])) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/pimd/PimdEngine.sol:792:9\n    │\n792 │         emit Pruned(a);\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/pimd/PimdEngine.sol:367:17\n    │\n367 │                 emit RegistrationDeferred(a, reclaimCursor);\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/pimd/PimdEngine.sol:378:13\n    │\n378 │             emit Registered(a);\n    │             ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdEngine.sol:372:24\n    │\n372 │             h.index1 = uint32(holders.length);\n    │                        ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' 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/pimd/PimdEngine.sol:376:25\n    │\n376 │             h.lastBal = uint128(bal);\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/pimd/PimdEngine.sol:377:29\n    │\n377 │             h.streakStart = uint64(block.timestamp); // the clock starts at registration, never earlier\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[calls-loop]: external call inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:403:17\n    │\n403 │             if (IERC20Min(token).balanceOf(a) >= minBalance && !_shapeChanged(a, _holder[a]) && !_isPool(a)) continue;\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-no-eth]: external call can be reentered before `phase` is updated\n    ╭▸ src/pimd/PimdEngine.sol:423:17\n    │\n423 │             try hook.flush() {}\n    │                 ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `phase` is updated\n    ╭▸ src/pimd/PimdEngine.sol:428:21\n    │\n428 │                 try hook.flushHolders() {} catch {}\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdEngine.sol:415:13\n    │\n415 │         if (block.timestamp < lastFire + minInterval) revert TooSoon();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/pimd/PimdEngine.sol:719:9\n    │\n719 │         emit Income(income);\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/pimd/PimdEngine.sol:443:9\n    │\n443 │         emit Fired(epoch + 1, drip, n);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/pimd/PimdHook.sol:360:44\n    │\n360 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdHook.sol:349:24\n    │\n349 │                     && block.timestamp < uint256(launchStart) + launchCapSeconds\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdHook.sol:350:24\n    │\n350 │                     && block.timestamp < uint256(launchStart) + LAUNCH_CAP_MAX_SECONDS\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/pimd/PimdHook.sol:528:9\n    │\n528 │         emit FeeTaken(isBuy, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdHook.sol:328:44\n    │\n328 │             uint256 movedQuote = a0q < 0 ? uint256(uint128(-a0q)) : uint256(uint128(a0q));\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/pimd/PimdHook.sol:328:52\n    │\n328 │             uint256 movedQuote = a0q < 0 ? uint256(uint128(-a0q)) : uint256(uint128(a0q));\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/pimd/PimdHook.sol:328:69\n    │\n328 │             uint256 movedQuote = a0q < 0 ? uint256(uint128(-a0q)) : uint256(uint128(a0q));\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/pimd/PimdHook.sol:328:77\n    │\n328 │             uint256 movedQuote = a0q < 0 ? uint256(uint128(-a0q)) : uint256(uint128(a0q));\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/pimd/PimdHook.sol:336:41\n    │\n336 │             uint256 quoteAmt = a0 < 0 ? uint256(uint128(-a0)) : uint256(uint128(a0));\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/pimd/PimdHook.sol:336:49\n    │\n336 │             uint256 quoteAmt = a0 < 0 ? uint256(uint128(-a0)) : uint256(uint128(a0));\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/pimd/PimdHook.sol:336:65\n    │\n336 │             uint256 quoteAmt = a0 < 0 ? uint256(uint128(-a0)) : uint256(uint128(a0));\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/pimd/PimdHook.sol:336:73\n    │\n336 │             uint256 quoteAmt = a0 < 0 ? uint256(uint128(-a0)) : uint256(uint128(a0));\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/pimd/PimdHook.sol:340:36\n    │\n340 │             hookDeltaUnspecified = int128(uint128(fee));\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/pimd/PimdHook.sol:340:43\n    │\n340 │             hookDeltaUnspecified = int128(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[reentrancy-no-eth]: external call can be reentered before `pot` is updated\n    ╭▸ src/pimd/PimdEngine.sol:751:13\n    │\n751 │             address(imd).call{gas: SEND_GAS}(abi.encodeCall(IERC20Min.transfer, (to, amount)));\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdHook.sol:352:33\n    │\n352 │                 uint256 spent = uint256(uint128(-delta.amount0())) + fee;\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/pimd/PimdHook.sol:352:41\n    │\n352 │                 uint256 spent = uint256(uint128(-delta.amount0())) + 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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/pimd/PimdEngine.sol:538:32\n    │\n538 │             h.weight = uint128(w);\n    │                                ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:523:27\n    │\n523 │             uint256 bal = IERC20Min(token).balanceOf(a);\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdEngine.sol:526:33\n    │\n526 │                 h.streakStart = uint64(nowTs); // sold or sent out: the clock restarts\n    │                                 ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/pimd/PimdEngine.sol:529:33\n    │\n529 │                 h.streakStart = uint64((last * h.streakStart + (bal - last) * nowTs) / bal);\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/pimd/PimdEngine.sol:535:25\n    │\n535 │             h.lastBal = uint128(bal);\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/pimd/PimdEngine.sol:538:24\n    │\n538 │             h.weight = uint128(w);\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 `cursor` is updated\n    ╭▸ src/pimd/PimdEngine.sol:751:13\n    │\n751 │             address(imd).call{gas: SEND_GAS}(abi.encodeCall(IERC20Min.transfer, (to, amount)));\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/pimd/PimdEngine.sol:593:19\n    │\n593 │                 ++paidHolders;\n    │                   ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/pimd/PimdEngine.sol:592:17\n    │\n592 │                 paid += amt;\n    │                 ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_lock` is updated\n    ╭▸ src/pimd/PimdHook.sol:433:9\n    │\n433 │         poolManager.unlock(abi.encode(ACTION_HOLDERS, toHolders));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/pimd/PimdEngine.sol:751:13\n    │\n751 │             address(imd).call{gas: SEND_GAS}(abi.encodeCall(IERC20Min.transfer, (to, amount)));\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/pimd/PimdHook.sol:437:9\n    │\n437 │         emit Flushed(msg.sender, toHolders, 0, 0);\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/pimd/PimdEngine.sol:596:17\n    │\n596 │                 emit PayoutFailed(a, amt);\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/pimd/PimdEngine.sol:612:13\n    │\n612 │             emit EpochPaid(epoch, epochPaidHolders == 0 ? 0 : total - leftover, epochPaidHolders);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/pimd/PimdHook.sol:433:9\n    │\n433 │         poolManager.unlock(abi.encode(ACTION_HOLDERS, toHolders));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdEngine.sol:591:31\n    │\n591 │                 h.received += uint128(amt);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdEngine.sol:646:13\n    │\n646 │         if (block.timestamp < lastFire + ABORT_DELAY) revert TooSoon();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_lock` is updated\n    ╭▸ src/pimd/PimdHook.sol:463:9\n    │\n463 │         poolManager.unlock(abi.encode(ACTION_FLUSH, toHolders, toTeam - tip, msg.sender, tip));\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/pimd/PimdHook.sol:471:9\n    │\n471 │         emit Flushed(msg.sender, toHolders, toTeam, tip);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/pimd/PimdHook.sol:463:9\n    │\n463 │         poolManager.unlock(abi.encode(ACTION_FLUSH, toHolders, toTeam - tip, msg.sender, tip));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdHook.sol:518:16\n    │\n518 │             && block.timestamp < uint256(launchStart) + launchCapSeconds\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/pimd/PimdHook.sol:519:16\n    │\n519 │             && block.timestamp < uint256(launchStart) + LAUNCH_CAP_MAX_SECONDS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/pimd/PimdEngine.sol:751:13\n    │\n751 │             address(imd).call{gas: SEND_GAS}(abi.encodeCall(IERC20Min.transfer, (to, amount)));\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/pimd/PimdEngine.sol:789:32\n    │\n789 │         _holder[last].index1 = uint32(idx0 + 1);\n    │                                ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' 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.timestamp` may be reused across `vm.warp`\n    ╭▸ test/pimd/PimdBase.t.sol:275:17\n    │\n275 │         vm.warp(block.timestamp + hook.launchCapSeconds() + 1);\n    │         ────────━━━━━━━━━━━━━━━─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/pimd/Pimd.t.sol:195:17\n    │\n195 │         vm.warp(block.timestamp + 15 days); // alice reaches 3x\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/pimd/Pimd.t.sol:237:17\n    │\n237 │         vm.warp(block.timestamp + 2 days); // alice is past the first hour, and this epoch clears the backlog\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/pimd/PimdAttacks.t.sol:352:17\n    │\n352 │         vm.warp(block.timestamp + 2 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/pimd/PimdFork.t.sol:236:17\n    │\n236 │         vm.warp(block.timestamp + 601);\n    │         ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2269,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/pimd/PimdFixes.t.sol:PimdReclaimCursorTest\n[PASS] test_a_dead_slot_behind_eight_live_ones_is_still_reclaimed() (gas: 2397157)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.06s (750.83µs CPU time)\n\nRan 32 tests for test/pimd/PimdFixes.t.sol:PimdFixesTest\n[PASS] test_a_7702_delegated_wallet_keeps_earning_and_its_streak() (gas: 1967115)\n[PASS] test_a_bag_that_arrives_just_before_the_tally_carries_no_weight() (gas: 2309640)\n[PASS] test_a_deferred_registration_says_so() (gas: 1898392)\n[PASS] test_a_fire_that_opens_no_epoch_emits_nothing() (gas: 839978)\n[PASS] test_a_fresh_buy_earns_from_the_next_epoch() (gas: 2463778)\n[PASS] test_a_null_epoch_pays_no_per_holder_tip() (gas: 1989243)\n[PASS] test_a_null_epoch_pays_the_firer_nothing() (gas: 1675717)\n[PASS] test_a_pay_page_cannot_take_more_than_its_share_of_the_tips() (gas: 3468786)\n[PASS] test_a_refused_team_transfer_does_not_stop_the_drip() (gas: 1460076)\n[PASS] test_a_stuck_epoch_can_be_abandoned_and_the_imd_comes_back() (gas: 1662026)\n[PASS] test_a_walked_bag_cannot_lock_the_holder_set_for_ever() (gas: 1167001)\n[PASS] test_abort_is_refused_when_no_epoch_is_open() (gas: 97252)\n[PASS] test_an_engine_with_no_keeper_cannot_be_deployed() (gas: 13974)\n[PASS] test_an_unbounded_holder_set_cannot_be_configured() (gas: 13371)\n[PASS] test_bind_refuses_a_token_the_hook_does_not_tax() (gas: 57104)\n[PASS] test_everything_except_tally_stays_permissionless() (gas: 1543866)\n[PASS] test_fire_does_not_tip_the_engine_out_of_the_team_slice() (gas: 1401450)\n[PASS] test_firing_into_an_empty_holder_set_pays_no_tip() (gas: 880400)\n[PASS] test_only_a_keeper_can_tally() (gas: 1285050)\n[PASS] test_pay_is_funded_even_when_the_firer_could_take_the_lot() (gas: 2836198)\n[PASS] test_prune_cannot_brick_a_running_epoch() (gas: 2375989)\n[PASS] test_prune_is_refused_during_pay_too() (gas: 1231303)\n[PASS] test_prune_refuses_while_the_pool_is_unlocked() (gas: 1146391)\n[PASS] test_prune_still_works_between_epochs() (gas: 1162415)\n[PASS] test_real_code_still_voids_the_vetted_shape() (gas: 910282)\n[PASS] test_register_refuses_while_the_pool_is_unlocked() (gas: 1037823)\n[PASS] test_the_firer_is_paid_not_the_tallier() (gas: 1254128)\n[PASS] test_the_holder_bound_fits_the_chains_per_transaction_budget() (gas: 14237)\n[PASS] test_the_holder_set_is_bounded() (gas: 1905902)\n[PASS] test_the_keeper_tip_cannot_take_the_whole_team_slice() (gas: 747118)\n[PASS] test_the_launch_factory_and_precompiles_can_never_register() (gas: 746476)\n[PASS] test_total_to_team_matches_what_the_team_received() (gas: 760503)\nSuite result: ok. 32 passed; 0 failed; 0 skipped; finished in 1.09s (33.35ms CPU time)\n\nRan 1 test for test/pimd/Gas.t.sol:GasTest\n[PASS] test_epoch_gas_for_100_holders() (gas: 45150869)\nLogs:\n  fire             408551\n  tally(100)       3489228\n  pay(100)         3562883\n  epoch total      7460662\n  per holder       70521\n  hook.flush       130242\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.09s (22.95ms CPU time)\n\nRan 6 tests for test/pimd/PimdFork.t.sol:PimdForkTest\n[PASS] test_fork_buy_takes_tax_in_real_imd() (gas: 665037)\n[PASS] test_fork_engine_drips_real_imd_to_a_holder() (gas: 1542262)\nLogs:\n  IMD paid to one holder (wei) 5621280779456078301\n\n[PASS] test_fork_flush_moves_real_imd() (gas: 892000)\n[PASS] test_fork_imd_is_a_plain_erc20_with_no_transfer_fee() (gas: 286859)\n[PASS] test_fork_launch_against_real_state() (gas: 41093)\n[PASS] test_fork_sell_takes_tax_in_real_imd() (gas: 945217)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 2.16s (1.45s CPU time)\n\nRan 37 tests for test/pimd/Pimd.t.sol:PimdTest\n[PASS] test_a_partially_filled_buy_is_refused_rather_than_overtaxed() (gas: 781742)\n[PASS] test_a_second_liquidity_add_is_refused() (gas: 448407)\n[PASS] test_a_second_pool_on_this_hook_is_refused() (gas: 47531)\n[PASS] test_a_stranger_cannot_squat_the_seed() (gas: 192940488)\n[PASS] test_an_excluded_holder_is_never_registered_or_paid() (gas: 638866)\n[PASS] test_bind_is_one_shot() (gas: 38573)\n[PASS] test_buy_pays_the_buy_tax_in_imd() (gas: 557742)\n[PASS] test_drip_releases_about_four_percent_per_fifteen_minutes() (gas: 2330929)\n[PASS] test_even_the_factory_gets_only_one_add() (gas: 63959)\n[PASS] test_exact_out_buy_is_taxed_too() (gas: 853740)\n[PASS] test_exact_out_sell_is_taxed_too() (gas: 820022)\n[PASS] test_fee_collection_is_still_allowed() (gas: 690210)\n[PASS] test_first_hour_pays_nobody() (gas: 1151822)\n[PASS] test_flush_payouts_moves_real_imd() (gas: 771924)\n[PASS] test_holder_is_paid_in_imd_after_a_day() (gas: 1447209)\n[PASS] test_hook_refuses_raw_eth() (gas: 36901)\n[PASS] test_initialize_accepts_the_briefed_price() (gas: 192505278)\n[PASS] test_initialize_does_not_gate_the_opening_price() (gas: 192510044)\n[PASS] test_initialize_refuses_a_pool_that_is_not_our_token() (gas: 193230153)\n[PASS] test_initialize_refuses_a_quote_that_is_not_imd() (gas: 193230211)\n[PASS] test_initialize_refuses_an_unsupported_fee_tier() (gas: 192332936)\n[PASS] test_keeper_tip_comes_out_of_the_team_slice() (gas: 764376)\n[PASS] test_launch_cap_expires() (gas: 879738)\n[PASS] test_launch_cap_limits_one_buyer() (gas: 748611)\n[PASS] test_launch_puts_the_seed_in_one_range() (gas: 35399)\n[PASS] test_launch_sets_imd_as_currency0() (gas: 9805)\n[PASS] test_liquidity_can_never_be_removed_even_by_its_owner() (gas: 137603)\n[PASS] test_longer_holder_gets_a_bigger_share() (gas: 1931055)\n[PASS] test_only_the_launch_factory_may_open_the_pool() (gas: 192333813)\n[PASS] test_pool_starts_with_no_imd() (gas: 10156)\n[PASS] test_sell_pays_the_sell_tax_in_imd() (gas: 831463)\n[PASS] test_selling_restarts_the_streak() (gas: 1448330)\n[PASS] test_tax_splits_seventy_five_twenty_five() (gas: 561800)\n[PASS] test_the_imd_address_is_never_registered() (gas: 644140)\n[PASS] test_the_tax_is_only_holders_and_team() (gas: 545056)\n[PASS] test_the_token_address_is_never_registered_or_paid() (gas: 1866914)\n[PASS] test_third_party_liquidity_is_blocked() (gas: 1605762)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 2.16s (7.69s CPU time)\n\nRan 14 tests for test/pimd/PimdAttacks.t.sol:PimdAttacksTest\n[PASS] test_a_holder_that_turns_pool_shaped_earns_nothing_at_once() (gas: 2098917)\n[PASS] test_a_holder_that_turns_pool_shaped_is_prunable() (gas: 1806634)\n[PASS] test_a_long_gap_still_releases_only_a_slice() (gas: 1470594)\n[PASS] test_a_pool_like_contract_is_not_paid() (gas: 1431205)\n[PASS] test_a_stranger_can_deploy_it_and_hold_nothing() (gas: 91050)\n[PASS] test_a_wallet_that_grows_code_is_not_stuck_at_zero() (gas: 2745684)\n[PASS] test_dust_holder_is_skipped_and_prunable() (gas: 742656)\n[PASS] test_fire_refuses_while_the_pool_is_unlocked() (gas: 1096957)\n[PASS] test_many_holders_weigh_whole_and_pay_across_pages() (gas: 54348333)\n[PASS] test_one_bag_cannot_be_weighed_twice_across_wallets() (gas: 1835876)\n[PASS] test_one_refused_holder_does_not_stop_the_others() (gas: 2399093)\n[PASS] test_pay_refuses_while_the_pool_is_unlocked() (gas: 1830241)\n[PASS] test_tally_refuses_while_the_pool_is_unlocked() (gas: 1655768)\n[PASS] test_team_is_excluded_from_drips() (gas: 26127)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 2.16s (41.76ms CPU time)\n\nRan 6 test suites in 2.16s (9.71s CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":77,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PimdEngine.abortEpoch()\",\"PimdEngine.bind(address,address,address[])\",\"PimdEngine.fire()\",\"PimdEngine.pay(uint256)\",\"PimdEngine.prune(address[])\",\"PimdEngine.register(address[])\",\"PimdEngine.seed(uint256)\",\"PimdEngine.tally(uint256)\",\"PimdHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"PimdHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"PimdHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"PimdHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"PimdHook.flush()\",\"PimdHook.flushHolders()\",\"PimdHook.receive()\",\"PimdHook.unlockCallback(bytes)\",\"PimdToken.approve(address,uint256)\",\"PimdToken.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"PimdToken.transfer(address,uint256)\",\"PimdToken.transferFrom(address,address,uint256)\"],\"files\":{\".audit/HOT_SPOTS.md\":65,\".audit/REVIEW.md\":218,\".audit/context/01-access-control.md\":612,\".audit/context/02-swap-accounting.md\":568,\".audit/context/03-engine-lifecycle.md\":559,\".audit/context/04-arithmetic.md\":585,\".audit/context/05-reentrancy-external.md\":605,\".audit/context/06-tests-and-deploy.md\":829,\".audit/poc/ZZArith.t.sol\":188,\".audit/poc/ZZArith2.t.sol\":78,\".gitignore\":5,\"LICENSE\":21,\"README.md\":142,\"foundry.toml\":35,\"launch.json\":34,\"script/DeployPimd.s.sol\":118,\"script/PimdRehearsal.sol\":121,\"script/RehearsalSeeder.sol\":71,\"script/RehearsePimd.s.sol\":93,\"src/libraries/L2Block.sol\":21,\"src/pimd/PimdEngine.sol\":880,\"src/pimd/PimdHook.sol\":553,\"src/pimd/PimdToken.sol\":49,\"test/pimd/Gas.t.sol\":46,\"test/pimd/Pimd.t.sol\":574,\"test/pimd/PimdAttacks.t.sol\":420,\"test/pimd/PimdBase.t.sol\":409,\"test/pimd/PimdFixes.t.sol\":1003,\"test/pimd/PimdFork.t.sol\":265},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f924a93fc4f015d0b1857e4e340dc7e33e07ac270647f999774df5469d1afc20","verifiedTreeHash":"09f3974f686acc48225e56ec6cbd927ed3fa2e3e","verifierVersion":"0.1.0+fb8278c5"}]}