·
We finished the Panoptic contest on Code4rena.
Lesson Summary:
- Verify pool ID packing/unpacking end-to-end — Confirm truncation (e.g., last 40 bits of Uniswap pool address), void bits (8-bit field), tick spacing (48-bit), and shift amounts (e.g., >>112, >>240) match exactly between encode (constructor/init) and decode (getters) to avoid silent truncation or ID collisions.
- Test collision handling in while loops rigorously — When incrementing pool patterns (+1 on lower bits) to avoid address clashes, fuzz edge cases (all-1s in 40-bit field, overflow into void/tick spacing bits) — confirm cast-back-to-uint40 correctly wraps without corrupting higher fields.
- Check initialization guards are tamper-proof — Ensure isInitialized bit (often left-shifted high, e.g., <<240) can’t be faked via direct storage writes or re-initialization attacks; verify it’s only set once during legitimate pool creation and unpacked correctly (e.g., >>240 > 0).
- Evaluate permissionless tick expansion impact — For functions like expand that any user can call, assess griefing vectors: flash-loan → massive deposit → expand ticks → withdraw → revert ranges; check if gas/refund costs or economic disincentives prevent abuse.
- Question uncapped or unchecked math in tick/liquidity bounds — Even if getApproxTickWithMaxAmount uses modular division or clamping, test whether totalSupply growth or extreme amounts can push computed ticks beyond Uniswap V3 valid ranges (despite no explicit cap) — trust but fuzz the “mathematical cap”.