From 63f9c6cd993daf95db3b5bd7d993a4e2829baa80 Mon Sep 17 00:00:00 2001 From: aaronjmars <61592645+aaronjmars@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:10:24 -0400 Subject: [PATCH] feat(audits): publish sc-audit reports for the 12 fleet hooks Add audits/ with one full autonomous sc-audit report per fleet hook (all CLEAN, 0 confirmed) plus an index README, and set auditUrl on each hooks/*.json so aeon.fun/hooks links each hook to its audit. Reports are the disclosure-safe public form: flaw + invariant + prose only, internal paths stripped. --- audits/README.md | 27 ++++++++++ audits/blockecho.md | 63 +++++++++++++++++++++++ audits/capgate.md | 52 +++++++++++++++++++ audits/crownclash.md | 67 ++++++++++++++++++++++++ audits/dailywindowgate.md | 60 +++++++++++++++++++++ audits/dynamicfee.md | 62 ++++++++++++++++++++++ audits/exactingate.md | 103 +++++++++++++++++++++++++++++++++++++ audits/heavierhand.md | 63 +++++++++++++++++++++++ audits/legacyledger.md | 61 ++++++++++++++++++++++ audits/markethoursgate.md | 62 ++++++++++++++++++++++ audits/noop.md | 61 ++++++++++++++++++++++ audits/tailtwins.md | 72 ++++++++++++++++++++++++++ audits/totalizertrap.md | 67 ++++++++++++++++++++++++ hooklist.json | 48 ++++++++--------- hooks/blockecho.json | 3 +- hooks/capgate.json | 3 +- hooks/crownclash.json | 3 +- hooks/dailywindowgate.json | 3 +- hooks/dynamicfee.json | 3 +- hooks/exactingate.json | 3 +- hooks/heavierhand.json | 3 +- hooks/legacyledger.json | 3 +- hooks/markethoursgate.json | 3 +- hooks/noop.json | 3 +- hooks/tailtwins.json | 3 +- hooks/totalizertrap.json | 3 +- 26 files changed, 868 insertions(+), 36 deletions(-) create mode 100644 audits/README.md create mode 100644 audits/blockecho.md create mode 100644 audits/capgate.md create mode 100644 audits/crownclash.md create mode 100644 audits/dailywindowgate.md create mode 100644 audits/dynamicfee.md create mode 100644 audits/exactingate.md create mode 100644 audits/heavierhand.md create mode 100644 audits/legacyledger.md create mode 100644 audits/markethoursgate.md create mode 100644 audits/noop.md create mode 100644 audits/tailtwins.md create mode 100644 audits/totalizertrap.md diff --git a/audits/README.md b/audits/README.md new file mode 100644 index 0000000..3ca2fca --- /dev/null +++ b/audits/README.md @@ -0,0 +1,27 @@ +# Fleet audits + +Autonomous `sc-audit` reports for the aeon.fun Uniswap v4 hook fleet. Each hook's +verified on-chain Base source was audited threat-model-first (Slither + an agentic +invariant / access-control / oracle pass + a fuzz arm gated on findings) against the +full 11-class v4-hook checklist, with every vendored `@uniswap/v4-core` file +SHA-256-diffed against the genuine npm release. **All 12 hooks: CLEAN (0 confirmed).** + +| Hook | Category | Full audit | Verdict | Date | +|------|----------|-----------|---------|------| +| BlockEcho | Games | [blockecho.md](./blockecho.md) | CLEAN (0) | 2026-09-07 | +| CapGate | Access | [capgate.md](./capgate.md) | CLEAN (0) | 2026-09-06 | +| CrownClash | Rewards | [crownclash.md](./crownclash.md) | CLEAN (0) | 2026-09-07 | +| DailyWindowGate | Access | [dailywindowgate.md](./dailywindowgate.md) | CLEAN (0) | 2026-09-07 | +| DynamicFee | Fees | [dynamicfee.md](./dynamicfee.md) | CLEAN (0) | 2026-09-07 | +| ExactInGate | Access | [exactingate.md](./exactingate.md) | CLEAN (0) | 2026-09-07 | +| HeavierHand | Access | [heavierhand.md](./heavierhand.md) | CLEAN (0) | 2026-09-07 | +| LegacyLedger | Rewards | [legacyledger.md](./legacyledger.md) | CLEAN (0) | 2026-09-07 | +| MarketHoursGate | Access | [markethoursgate.md](./markethoursgate.md) | CLEAN (0) | 2026-09-07 | +| NoOp | Access | [noop.md](./noop.md) | CLEAN (0) | 2026-09-07 | +| TailTwins | Games | [tailtwins.md](./tailtwins.md) | CLEAN (0) | 2026-09-07 | +| TotalizerTrap | Games | [totalizertrap.md](./totalizertrap.md) | CLEAN (0) | 2026-09-07 | + +These are autonomous agent audits: thorough and case-exhaustive at the source level, +but not a substitute for a paid human audit. Each report's *Coverage and limitations* +section states exactly what was and was not machine-proven. Findings on first-party +hooks are operator-gated by policy; nothing here is under embargo (all clean). diff --git a/audits/blockecho.md b/audits/blockecho.md new file mode 100644 index 0000000..3c446ea --- /dev/null +++ b/audits/blockecho.md @@ -0,0 +1,63 @@ +# sc-audit report: base:0x5e48f905661D75501CA756eDB3403dA98F0400C4 (BlockEcho) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + provenance) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0x5e48f905661D75501CA756eDB3403dA98F0400C4 (BlockEcho - aeon.fun Uniswap v4 hook) +- On-chain context: non-proxy; native balance 0 wei (no custody at rest); poolManager = 0x498581fF718922c3f8e6A244956aF099B2652b2b (canonical Uniswap v4 PoolManager on Base, genuine/immutable) +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); operator-gated regardless (operator's own hook). Ledger row + coverage manifest written. + +## 1. Executive summary +BlockEcho is a stateless Uniswap v4 gate hook built on aeon's shared `AeonFee` base. Its gimmick: a swap only clears `beforeSwap` if the absolute swap amount's last two decimal digits echo the current block number's last two digits, within a circular tolerance of 2 (`ECHO_MODULUS = 100`, `ECHO_WINDOW = 2`). `afterSwap` (inherited, non-virtual) takes the mandatory 10 bps protocol fee on the unspecified currency and routes it to the fixed treasury. This is the third independent audit of this exact deployed address (previously clean 2026-08-19 and 2026-08-24); it was re-run at the operator's explicit `var=` request inside the 30-day dedup window. The deployed bytecode is immutable, so the source is byte-identical to prior runs, but reasoning was re-derived independently and on-chain context + provenance were re-verified fresh. Verdict: clean - no path breaks any modeled invariant; the gate is a liveness gimmick on the swapper's own amount with no custody, no admin surface, and no fund-loss vector. + +## 2. Scope +- Contracts reviewed: 2/2 (BlockEcho.sol + AeonFee.sol, ~191 LOC production) +- Entrypoints reviewed: 6/6 - 2 state-changing hook callbacks (`beforeSwap`, `afterSwap`, both `onlyPoolManager`) + 4 public `view` quote helpers (`requiredSuffix`, `isAcceptable`, `acceptableAmountAtOrAbove`, `blocksUntilAcceptable`) +- Address audited: 0x5e48f905661D75501CA756eDB3403dA98F0400C4 (Base, chainid 8453); bytecode 1992 bytes, immutable (non-proxy) +- Not reviewed this run: 14 vendored `@uniswap/v4-core` files (checked for provenance only, not re-audited - they are byte-identical to upstream 1.0.1). No admin/owner/pause/upgrade/init function exists to review - the hook has none. + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5) across the full v4-hook checklist, adversarially refuted the two raised edge cases (S6). Fuzz arm not run (hard gate - 0 survivors). +- Tools: slither(ok - 2 hits, both false positives), agentic(ok), fuzz(not-run - clean-audit hard gate) +- Provenance (onchain): 14/14 vendored `@uniswap/v4-core` files SHA-256-IDENTICAL to genuine npm 1.0.1 (and cross-checked against 1.0.0); no supply-chain tampering. `AEON_FEE_RECIPIENT` = 0xF1E958db7D1e4C074377946018Ad645db4FB158e (aeon treasury); `poolManager` re-derived via Base RPC `eth_call` resolves to the canonical Base v4 PoolManager. +- Address-flag check: low-14-bit suffix `0x00C4` = BEFORE_SWAP + AFTER_SWAP + AFTER_SWAP_RETURNS_DELTA, an exact match to the two implemented callbacks; BEFORE_SWAP_RETURNS_DELTA (0x08) correctly unset since `beforeSwap` returns `ZERO_DELTA`. + +## 4. Threat model and invariants +Actors: **anyone** (a swapper, routed in by the PoolManager; governs only whether their own amount clears the gate); **PoolManager** (the sole caller allowed past `onlyPoolManager`); **AEON_FEE_RECIPIENT** (immutable treasury that receives the 10 bps fee). Value/authority crosses a boundary at exactly two points - `afterSwap`'s `poolManager.take()` of the fee, and `beforeSwap`'s accept/reject decision. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | `beforeSwap`/`afterSwap` callable only by the PoolManager | spoofed calls could fake gate/fee accounting | +| INV2 | 10 bps fee rate + recipient immutable and non-redirectable | fee theft / protocol revenue loss | +| INV3 | fee charged on the correct unspecified currency even on exact-output swaps | silent fee-skip / wrong-token charge | +| INV4 | gate never permanently bricks or one-directionally locks the pool | permanent DoS | +| INV5 | no swapper can grief another swapper's swap via the gate | griefing | +| INV6 | returned `afterSwap` delta equals what `poolManager.take` removed | delta mismatch drains/bricks the pool | +| INV7 | no reentrancy / state-corruption surface | accounting corruption | +| INV8 | address permission flags (0x00C4) exactly match implemented callbacks | flag mismatch mis-wires calls / fail-open | + +All eight hold - see §5/§6. + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +- **[none promoted]** `int128.min` / `int256.min` negation in `_size`/`AeonFee` - under solc 0.8.26 checked arithmetic, negating the minimum value reverts. This would revert only a single absurd-magnitude swap (~1.7e38+), unreachable in any real pool; not exploitable, not a DoS of legitimate flow. Refuted. +- **[none promoted]** `acceptableAmountAtOrAbove(target)` can overflow-revert when `target` is within ~100 of `uint256.max` - a `view` quote helper called with a nonsensical argument (no swap amount is near 2^256). DoS-on-nonsense-input only, off any value path. Refuted. +- **Slither: divide-before-multiply** in `CustomRevert.bubbleUpAndRevertWith` - the canonical `(returndatasize()+31)/32*32` round-up-to-word assembly idiom, inside a SHA-verified-identical upstream v4-core file. False positive. +- **Slither: incorrect-equality** `candidate == 0` in `acceptableAmountAtOrAbove` - the strict equality is precisely the intended wrap-guard (bump a zero candidate to `ECHO_MODULUS`) in a read-only helper. False positive. + +## 6. Coverage and limitations +- Explored: **access control** (both callbacks `onlyPoolManager`; the Cork-$12M missing-guard class - present and correct; no owner/init/upgrade surface); **fee direction** (all 4 zeroForOne × exact-in/out cases re-derived - unspecified currency selected correctly every time; `abs` applied before the `>0` guard so exact-output is never fee-skipped); **gate logic / unit-confusion** (the echo is a modular match on the swapper's own amount, not a token-denominated cap nor a price/skew gate - the raw-price-vs-1.0 and sub-18-decimal fail-open classes do not apply; binds symmetrically across both tokens and both directions); **rounding** (fee floors down, never over-charges; `require(feeAmount <= int128.max)` before the cast); **reentrancy** (`beforeSwap` view; `afterSwap` single trusted `take()` with no subsequent mutation, no `_afterSwapExtra` override); **flash-accounting** (returned delta == taken amount); **liveness/DoS** (echo gate admits some amount at every block within window, both directions; view helpers hand callers a clearing amount → no permanent brick); **MEV/griefing** (stateless, so no cross-swap interference; `block.number` is not attacker-chosen). Provenance verified by SHA-256 diff against genuine npm. +- Not exercised: fuzz arm intentionally skipped (clean-audit hard gate, S6.5). The 14 vendored v4-core files were provenance-checked, not re-audited. +- Honest partiality: a paid human audit would additionally add on-chain fork simulation of live swaps across pool configurations and an economic model of the fee interaction with pool fee tiers. Given the trivial, stateless, custody-free surface and immutable bytecode already twice-audited clean, residual risk is very low. + +## 7. Appendix + +- Contract: [`0x5e48f905661D75501CA756eDB3403dA98F0400C4` on BaseScan](https://basescan.org/address/0x5e48f905661D75501CA756eDB3403dA98F0400C4) - verified source. +- Registry entry: [`hooks/blockecho.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/blockecho.json) - flags + every-chain addresses. +- Source: [`src/BlockEcho.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/BlockEcho.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/capgate.md b/audits/capgate.md new file mode 100644 index 0000000..68d7918 --- /dev/null +++ b/audits/capgate.md @@ -0,0 +1,52 @@ +# sc-audit report: base:0xa12bf4fc954b37cbe7acc2fa652328071f8b00c4 (CapGate) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-06 (re-confirmed 2026-09-07 via dedup; bytecode immutable, source unchanged) · Mode: onchain +- Target: 0xa12bF4fC954B37cbe7Acc2fA652328071F8b00c4 (CapGate - Uniswap v4 hook) +- On-chain context: non-proxy; compiler v0.8.26+commit.8a97fa7a, cancun; flags 0x00C4 (BEFORE_SWAP + AFTER_SWAP + AFTER_SWAP_RETURNS_DELTA) - exact match to the implemented callbacks; beforeSwap returns ZERO_DELTA so no BEFORE_SWAP_RETURNS_DELTA bit is set, afterSwap returns the fee delta so AFTER_SWAP_RETURNS_DELTA is set; fee recipient 0xF1E958db7D1e4C074377946018Ad645db4FB158e (matches operator treasury of record); no custody in the hook. +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); MODE=onchain operator-gated regardless - operator's own aeon.fun Base hook. + +## 1. Executive summary +CapGate is a Uniswap v4 hook that rejects any swap larger than a per-side size cap - Uniswap v4 hook (per-side size cap: reject swaps > 5% of the specified currency's virtual reserve + mandatory 10bps AeonFee). It inherits the mandatory, non-redirectable 10 bps `AeonFee` on the swap's unspecified currency. This audit re-fetched the verified source, ran the full 11-class v4-hook checklist, SHA-256-diffed every vendored dependency, and read the live on-chain constants. Verdict: **clean, 0 confirmed**. The key point: the cap is dimensionally consistent - it compares the swap size to the virtual reserve of the *same* currency the amount is denominated in, so it is the corrected form of the "raw-token-cap" class that silently failed open on sub-18-decimal tokens. + +## 2. Scope +- Contracts reviewed: 2/2 (2 production .sol files, ~204 LOC) - src/CapGate.sol, src/AeonFee.sol +- Entrypoints reviewed: 5/5 (beforeSwap(view,onlyPoolManager), afterSwap(onlyPoolManager) + view getters reserves, maxTradeSize, _reserves(internal)) +- Address audited: 0xa12bF4fC954B37cbe7Acc2fA652328071F8b00c4 (Base, chainid 8453); deployed bytecode is immutable +- Not reviewed this run: the 20 vendored `@uniswap/v4-core` files are treated as dependencies - provenance-checked by SHA-256 (below) rather than re-audited (canonical upstream). + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), and adversarially refuted each candidate class (S6). No survivor qualified for the fuzz arm (0 confirmed -> hard gate). +- Tools: slither(ok - forge build succeeded (22 files, solc 0.8.26, remapping @uniswap/v4-core/=src/lib/v4-core/); 11 results, ALL false positives (10 in vendored v4-core FullMath/CustomRevert = canonical Bloemen mulDiv bit-hacks on files proven SHA-256-identical to upstream; 1 = CapGate._reserves intentionally destructuring only sqrtPriceX96 from getSlot0)), agentic(ok), fuzz(skipped (clean-audit hard gate - 0 survivors)) +- Provenance (MODE=onchain): 20/20 IDENTICAL to npm @uniswap/v4-core 1.0.1 - every vendored file byte-matches upstream. Security-critical StateLibrary (price/liquidity reads the cap depends on) and FullMath (the cap arithmetic) both IDENTICAL. +- Fee-recipient check: AEON_FEE_RECIPIENT constant == operator treasury 0xF1E958...B158e - no redirected-fee backdoor + +## 4. Threat model and invariants +Actors and trust boundaries: swappers (anyone) trade through pools adopting the hook, subject to the per-side size cap + 10 bps fee. The canonical Base PoolManager is the sole authorized caller of both callbacks (`onlyPoolManager`). The fee recipient is a fixed treasury EOA. The hook holds no custody. All 8 modeled invariants hold; the verifications: + +- CapGate is a per-side SIZE cap: beforeSwap (view) reverts TradeTooLarge when the swap's |amountSpecified| exceeds MAX_TRADE_BPS (500 = 5%) of the pool's virtual reserve of the SAME currency the amount is denominated in. This is the corrected sibling of the old raw-token-cap CapGate (MAX_TRADE=100e18) - the header comment documents that the raw cap 'silently did nothing on any pool whose specified token had <18 decimals'. +- INV3 (dimensional consistency) VERIFIED: specifiedIsZero = (params.zeroForOne == exactIn) where exactIn = amountSpecified<0. Checked all 4 (direction x exact-in/out) cases against the comment table - correct in every case. reserve is picked as b0 (currency0 units) or b1 (currency1 units) to MATCH the specified currency, so `size > cap` compares like units. The cap therefore binds symmetrically in both directions and both exact-in/out, and does NOT fail open on sub-18-decimal tokens. This is exactly the unit-confusion class the hook-checklist flags - CapGate is on the correct side of it. +- INV4 (never fully bricked) VERIFIED: reserve==0 (uninitialized / no in-range liquidity) returns ZERO_DELTA (open); reserve>0 gives cap = reserve/20 > 0 for any realistic pool, so swaps up to 5% always pass. Degenerate edge: virtual reserve in 1..19 wei -> cap floors to 0 -> all non-zero swaps blocked, but that is a dust/dead pool reachable only by an LP draining their OWN liquidity to near-zero (no external griefing path, no funds at risk) - a hardening observation, not a finding. +- INV2/INV5/INV6 (AeonFee) VERIFIED: fee currency = unspecified side selected via ((amountSpecified<0)==zeroForOne)?currency1:currency0 (all 4 cases checked correct); fee = floor(|unspecified|*10/10000) taken via poolManager.take() to the fixed constant recipient. afterSwap is NON-virtual and CapGate does NOT override _afterSwapExtra (default returns 0), so the fee cannot be lowered/skipped/redirected. int128.min hardened (widen to int256 before negate; require feeAmount<=int128.max). Returned feeDelta == amount take()n, no settlement mismatch. This AeonFee base is functionally identical to the clean 2026-09-06 HeavierHand audit. +- INV1/INV7 (access / reentrancy) VERIFIED: both callbacks carry onlyPoolManager; the two getters (reserves, maxTradeSize) are permissionless view with no state change. The only external call is poolManager.take() to a fixed EOA treasury (no callback) inside the PM swap lock; beforeSwap is view. No state to corrupt (all params are constants/immutable) - no reentrancy or read-only-reentrancy vector. +- Hook-checklist (11 classes) all clear: access-control (onlyPoolManager present, no Cork-class gap); hookData ignored, PoolKey supplied by trusted PM; flash-accounting single matching take (no CELO double-settle); rounding favors the stricter cap / standard fee floor; permission-bit vs address-flag exact match 0x00C4; dynamic-fee override returns 0 (only relevant if a pool operator pairs it with a dynamic-fee pool - a pairing choice, not a hook flaw); JIT-liquidity could raise one's own cap but is self-defeating with no drain; tick/price manipulation only loosens a size cap (no drain); unit-confusion FIXED (the design point); DoS/economic covered by INV4/INV5. +- Non-findings (design-intent / not disclosable): (a) dust-pool cap=0 edge (no external path, no funds); (b) beforeSwap dynamic-fee override of 0 (deployment pairing choice, matches HeavierHand). + +## 5. Findings +No confirmed findings. 0 candidates raised, 0 promoted. Each v4-hook checklist class was checked and refuted at the source (see the verifications above). + +## 6. Coverage and limitations +- Explored: access control, reentrancy, oracle/price manipulation, arithmetic/precision, upgradeability (none), external-call assumptions, economic/MEV, and the full 11-class v4-hook checklist (permission-bit encoding, flash-accounting delta/settlement, gate unit-confusion, tick/price manipulation). +- Not exercised: no fuzz campaign (clean-audit hard gate - 0 survivors). Slither ran (ok) with all 11 results confirmed false positives. +- Honest partiality: a paid human audit would add a live forge/echidna harness proving the cap binds symmetrically and the fee-currency correctness with a negative control. The reasoning here is source-level and case-exhaustive but not machine-proven by a fuzz campaign this run. + +## 7. Appendix + +- Contract: [`0xa12bF4fC954B37cbe7Acc2fA652328071F8b00c4` on BaseScan](https://basescan.org/address/0xa12bF4fC954B37cbe7Acc2fA652328071F8b00c4) - verified source. +- Registry entry: [`hooks/capgate.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/capgate.json) - flags + every-chain addresses. +- Source: [`src/CapGate.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/CapGate.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/crownclash.md b/audits/crownclash.md new file mode 100644 index 0000000..52d6cc6 --- /dev/null +++ b/audits/crownclash.md @@ -0,0 +1,67 @@ +# sc-audit report: base:0xD24D29a47Adb8786072Ab2Cb9925dC8Ba36Bc044 (CrownClash) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + provenance) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0xD24D29a47Adb8786072Ab2Cb9925dC8Ba36Bc044 (CrownClash - aeon.fun Uniswap v4 hook) +- On-chain context: non-proxy; native balance **0 ETH** (custody-free); PoolManager `0x498581ff718922c3f8e6a244956af099b2652b2b` (canonical Base v4); solc 0.8.26 / cancun; tx_count 1 (deploy only) +- Outcome: **CLEAN (0 confirmed)** +- Disclosure: none (clean); operator-gated by default (own contract) + +## 1. Executive summary +CrownClash is a custody-free Uniswap v4 `afterSwap` hook on Base: it takes the mandatory 10 bps AeonFee plus its own 0.07% "tribute" on every swap - both routed straight to the aeon treasury via `poolManager.take()` - and maintains an on-chain, bragging-rights volume leaderboard (the "crown"). This is an explicit operator re-audit of an address previously scanned clean on 2026-08-21 and 2026-08-24; fresh verified source was re-fetched this run (the fleet redeployed on/before 2026-09-06). Re-verified end-to-end against the live verified source, on-chain state, and dependency provenance: **no confirmed findings**. The single most important property - a return-delta hook must never revert or return a mismatched delta (which would brick the pool) - holds, and the hook holds no funds at any point (confirmed 0 native balance on-chain). + +## 2. Scope +- Contracts reviewed: **2/2** (CrownClash.sol + AeonFee.sol; ~110 LOC of production logic across 16 .sol files) +- Entrypoints reviewed: **2/2** (`afterSwap` [onlyPoolManager], `volumeGapToDethrone` [view]; `_afterSwapExtra` is internal, driven by afterSwap) +- Address audited: `0xD24D29a47Adb8786072Ab2Cb9925dC8Ba36Bc044` (Base, cid 8453) +- Not reviewed this run: the 14 vendored `@uniswap/v4-core` library/interface files were checked for **provenance only** (SHA-256 vs genuine npm), not re-audited as original code - they are upstream dependencies. + +## 3. Methodology +Threat-model-first: derived **8 invariants** (S5.0), hunted a path breaking each against the full v4-hook checklist (S5), adversarially refuted every candidate (S6). Fuzz arm skipped by the hard gate (0 survivors). +- Tools: slither(**ok**), agentic(**ok**), fuzz(not-run - clean-audit hard gate) +- Provenance (MODE=onchain): **14/14** vendored v4-core files byte-identical (SHA-256) to genuine npm `@uniswap/v4-core` **1.0.0 AND 1.0.1**. Two files (IHooks.sol, IPoolManager.sol) differ under the newer **1.0.2** only because upstream changed those interfaces in 1.0.2 - the contract vendored the 1.0.0/1.0.1 versions, both exact matches. **No supply-chain tampering**: every vendored file matches a genuine upstream release. +- On-chain verification: `poolManager()` eth_call → `0x498581ff…b2b` (canonical Base PoolManager, immutable); EIP-1967 slot all-zero (non-proxy, corroborates Etherscan Proxy=0); native balance 0; tx_count 1. +- Slither: build succeeded (`forge` staged this run) via a `@uniswap/v4-core/=src/lib/v4-core/` remapping - a genuine `SLITHER=ok`, better than the prior runs' `compile-fail` on this same layout. + +## 4. Threat model and invariants +Actors & trust boundaries: **anyone** can swap the pool (indirectly triggering afterSwap) and optionally name a `player` in hookData; only the **immutable PoolManager** may call `afterSwap` (it passes the real PoolKey/params/delta); the **treasury** (`0xF1E958…B158e`, a compile-time constant) receives fee+tribute and holds no privileged call. Value/authority crosses a boundary at exactly one point: `poolManager.take()`, which moves the fee and tribute straight out to the treasury. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | `afterSwap` only enterable by the PoolManager | a spoofed caller could forge a delta / abuse take() | +| INV2 | mandatory 10 bps fee cannot be lowered/skipped/redirected | protocol revenue integrity (non-virtual, constants) | +| INV3 | returned hookDelta == exactly what was take()n, on the unspecified currency, exact-in & exact-out | mismatched delta breaks flash-accounting → reverts every swap | +| INV4 | afterSwap never reverts for a realistic swap | a reverting return-delta hook bricks the whole pool | +| INV5 | hook custodies no funds (fee+tribute route straight to treasury) | no pot to strand, no rescue path to get wrong | +| INV6 | fee/tribute arithmetic can't overflow int128; int128.min negation hardened | overflow revert = pool DoS | +| INV7 | state writes precede the single external take() (CEI) | no reentrancy window on leaderboard/accounting | +| INV8 | champion/volume is bragging-rights only; no value distributed by it | hookData spoofing & wash-trading are benign, not theft | + +## 5. Findings +**No confirmed findings.** + +### Candidates raised and refuted +- **`afterSwap` caller spoofing (access-control, would-be critical)** - refuted: `onlyPoolManager` reverts `NotPoolManager` for any other caller; `poolManager` is immutable and verified on-chain to be the canonical Base PoolManager. (INV1) +- **Derived hook redirecting/lowering the 10 bps fee (access-control/economic, would-be high)** - refuted: `AeonFee.afterSwap` is non-virtual; recipient and rate are compile-time constants; `_afterSwapExtra` only runs *after* the fee is taken and can only add its own delta. (INV2) +- **Delta mismatch / double-settle across two currencies (flash-accounting, would-be critical/DoS)** - refuted: fee and tribute currencies are computed by the identical ternary → the same unspecified currency; both amounts are actually `take()`n and their positive sum is exactly the returned delta. (INV3) +- **Pool-bricking revert (DoS, would-be high)** - refuted: the only revert paths are the two overflow guards and the `int128.min` negation edge, all unreachable for any realistically-sized swap; rounding is down, never over-taking. (INV4/INV6) +- **Stranded funds / missing rescue (custody, would-be medium)** - refuted: hook holds nothing (0 native balance verified); every skim routes straight to the treasury; no withdraw/rescue path needed or present. (INV5) +- **Reentrancy on leaderboard/accounting (reentrancy, would-be high)** - refuted: all state writes precede the single `take()`; PoolManager is locked mid-swap. (INV7) +- **hookData `player` spoofing (would-be medium)** - refuted as benign: crediting an arbitrary address costs the caller gas+tribute and yields no monetary benefit; champion is bragging-rights state only. (INV8) +- **Wash-trading to take the crown (would-be low)** - refuted as intentional, disclosed game design; no funds distributed by champion status. (INV8) +- **Slither `divide-before-multiply` in `CustomRevert.sol`** - refuted: canonical `(returndatasize()+31)/32*32` word-alignment rounding in vendored, SHA-verified upstream v4-core; not in production code; false positive. + +## 6. Coverage and limitations +- **Explored:** access control, reentrancy (incl. read-only / CEI), oracle/price (N/A - hook reads no price/oracle/reserves), arithmetic/precision (fee/tribute rounding, int128.min edge, overflow guards), upgradeability (N/A - non-proxy, no delegatecall, EIP-1967 slot empty), external-call assumptions (single `take()` into trusted PoolManager), signatures/replay (N/A - no signatures), economic/MEV (wash-trading, hookData spoofing), and the **full v4-hook checklist** (permission-bit encoding `0xc044`→`0x0044` matches the sole afterSwap callback; flash-accounting delta sign/currency; no gate → no unit-confusion class). Slither: 1 hit, a vendored false positive. +- **Not exercised:** fuzz arm skipped by the hard gate (0 survivors - nothing to prove). The 14 vendored v4-core files were provenance-checked, not re-audited as source. +- **Honest partiality:** a paid human audit would additionally run a live fork-simulation of exact-in/exact-out swaps in both directions to observe the settled deltas end-to-end, and independently model interactions with a hostile custom token (fee-on-transfer / rebasing) as one leg of the pool - although the take-on-output pattern and 0-custody design bound that exposure to the treasury's own skim, not user principal. + +## 7. Appendix + +- Contract: [`0xD24D29a47Adb8786072Ab2Cb9925dC8Ba36Bc044` on BaseScan](https://basescan.org/address/0xD24D29a47Adb8786072Ab2Cb9925dC8Ba36Bc044) - verified source. +- Registry entry: [`hooks/crownclash.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/crownclash.json) - flags + every-chain addresses. +- Source: [`src/CrownClash.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/CrownClash.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/dailywindowgate.md b/audits/dailywindowgate.md new file mode 100644 index 0000000..e80fe40 --- /dev/null +++ b/audits/dailywindowgate.md @@ -0,0 +1,60 @@ +# sc-audit report: base:0x752B180116f5110dCBEa9564a43ACBEF82ebc080 (Hook / DailyWindowGate) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0x752B180116f5110dCBEa9564a43ACBEF82ebc080 (contract name `Hook`, "DailyWindowGate") +- On-chain context: non-proxy; single immutable `poolManager` = `0x498581ff718922c3f8e6a244956af099b2652b2b` (canonical Base Uniswap v4 PoolManager); native balance **0 ETH**; **no token custody** (funds at risk: 0) +- Outcome: **CLEAN (0 confirmed)** +- Disclosure: none (clean). MODE=onchain is operator-gated but there is nothing to stage. + +## 1. Executive summary +`Hook` is a freeform single-callback Uniswap v4 hook that enforces a **daily 10-minute UTC trading window**: a swap clears only while `block.timestamp % 86400 ∈ [00:00:00, 00:10:00)`, and reverts every other second of the day. It is one of the operator's own aeon.fun Base hooks, deployed 2026-09-04 by `deploy-uni-hook` (distinct from the AeonFee fleet - this one is a pure time gate with flag set `0x0080`, BEFORE_SWAP only). The hook holds no funds, takes no custody, has no owner/admin/upgrade path, and never gates liquidity provision. Verified source matches deployed bytecode (Etherscan V2); all 22 vendored `@uniswap/v4-core` files are byte-identical to the genuine npm 1.0.1 release. Every modeled invariant holds; the single production-code Slither hit (`weak-prng` on the intentional time-clock) is a false positive. **Verdict: clean.** + +## 2. Scope +- Contracts reviewed: **1/1** (production `src/Hook.sol`, ~65 LOC of body) + 22 vendored `@uniswap/v4-core` dependency files (provenance-verified, not re-audited as they are unmodified upstream) +- Entrypoints reviewed: **6/6** - `beforeSwap` (the only state-touching, attacker-reachable entrypoint), `isTradingOpen(uint256)` (pure), and the four auto-generated public getters (`poolManager`, `DAY`, `WINDOW_OPEN`, `WINDOW_CLOSE`) +- Address audited: `0x752B180116f5110dCBEa9564a43ACBEF82ebc080` (Base, chainid 8453) +- Not reviewed this run: none omitted from the production surface. Vendored v4-core libraries were provenance-diffed rather than line-audited (unmodified upstream, and none but `PoolIdLibrary.toId` and the `ZERO_DELTA` constant actually execute in this hook's paths). + +## 3. Methodology +Threat-model-first: derived 7 invariants (S5.0), hunted a path breaking each (S5) against the full 11-class v4-hook checklist, adversarially refuted candidates (S6). No survivor qualified for the fuzz arm (S6.5 hard gate: 0 confirmed → no fuzz). +- Tools: slither(**ok**), agentic(**ok**), fuzz(**skipped** - clean-audit hard gate) +- Provenance (MODE=onchain): **22/22 vendored `@uniswap/v4-core` files SHA-256 IDENTICAL to npm 1.0.1** (0 DIFFERENT, 0 UNVERIFIED). Diffing against 1.0.2 shows exactly 2 files differ (`IHooks.sol`, `IPoolManager.sol`), which simply confirms the pin at 1.0.1. Deployed source == bytecode via Etherscan V2 verification. +- Build: `forge build` succeeded (solc 0.8.26, cancun, v4-core remapping) → Slither reached a genuine `SLITHER=ok` (not compile-fail). + +## 4. Threat model and invariants +Actors and trust boundaries: anyone can swap through a pool that installs this hook (indirectly reaching `beforeSwap`); the canonical Base v4 PoolManager is the **sole** direct caller of `beforeSwap` (enforced by `onlyPoolManager` + immutable PM address); LPs may add/remove liquidity at any time (no liquidity callbacks are implemented, so those paths never reach the hook). No owner, admin, keeper, or upgrade authority exists. No value ever crosses into the hook (zero delta, zero fee, no take/settle). + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | `beforeSwap` callable only by the canonical PoolManager (immutable `poolManager`) | spoofed caller could bypass intent / emit fake events | +| INV2 | `beforeSwap` returns `IHooks.beforeSwap.selector` | wrong selector bricks every swap on the pool | +| INV3 | `beforeSwap` returns ZERO `BeforeSwapDelta` and 0 fee override; hook never `take()`/`settle()`s | any nonzero delta or custody path = fund manipulation | +| INV4 | liquidity add/remove is never gated (only flag `0x0080` set) | LP lock-in = fund-lock griefing | +| INV5 | a swap clears iff `(block.timestamp % 86400) ∈ [0,600)`, evaluated at execution | the gate's whole purpose; must be direction/token-agnostic and drift-free | +| INV6 | address low-14-bits (`0xc080 & 0x3FFF = 0x0080`) equals the implemented callback set (beforeSwap only) | flag/callback mismatch = uncalled callback or spurious revert | +| INV7 | a closed-window swap reverts only that tx; pool state, next-window swaps, and liquidity are unaffected | no permanent brick | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +- **[would-be LOW] `weak-prng` on `block.timestamp % DAY` (`src/Hook.sol:93`, Slither).** Refuted: `block.timestamp` is used here as a **UTC wall-clock**, not as a randomness/entropy source - the result feeds a time-window comparison, not a lottery/selection. Sequencer/validator timestamp influence on an OP-stack L2 is bounded to a few seconds and can at most shift a swap by seconds across the window boundary; it cannot drain funds (none held), seize authority (none exists), or brick the pool (LPs ungated; a revert affects only the current tx). This is the correct, idiomatic pattern for a time gate and is documented in the source. Not a vulnerability. +- **[would-be design-note] Swaps revert ~99.3% of the day.** Refuted as by-design: a daily 10-minute window is the hook's entire specified function, not a DoS. Liquidity is ungated by construction (no liquidity callbacks), so no LP can ever be locked; closed-window reverts are transient and self-healing at the next window. +- **Vendored-library Slither hits (15).** All in `@uniswap/v4-core` bit-hack libraries - `BitMath` incorrect-shift (×2), `FullMath` incorrect-exp (×1, the canonical `(3*denominator)^2` XOR-as-intended pattern), and `FullMath`/`TickMath`/`CustomRevert` divide-before-multiply (×12). Refuted by provenance: every one of these files is SHA-256 identical to the genuine npm 1.0.1 release; these are long-standing, well-understood false positives on canonical Uniswap math. None execute in this hook's paths anyway (only `PoolIdLibrary.toId` for the event and the `ZERO_DELTA` constant are reached). + +## 6. Coverage and limitations +- Explored: access control (`onlyPoolManager` + immutable canonical PM - INV1); selector integrity (INV2); flash-accounting / custody (returns `ZERO_DELTA` + 0 fee, no settle/sync/take/clear, no BEFORE_SWAP_RETURNS_DELTA flag - INV3); LP liveness (only `0x0080` flag, no liquidity callbacks - INV4); gate correctness and **unit-confusion** (this is a pure *time* gate - no `amountSpecified` cap, no reserve/skew comparison, no `balanceOf(poolManager)` - so it is inherently direction- and token-agnostic and immune to the amount/reserve unit-confusion class that has bitten other gates - INV5); permission-bit vs address-flag encoding (`0xc080 & 0x3FFF == 0x0080` matches the single implemented callback - INV6); reentrancy (no external calls in `beforeSwap`, only an event emit); dynamic fees (returns 0 without OVERRIDE flag - no override); no permanent brick (INV7). Full v4-hook checklist applied; provenance diff performed first. +- Slither hit classes: 16 total, **all false positives** - 15 vendored-library math bit-hacks (SHA-verified upstream) + 1 `weak-prng` on the intentional time-clock. +- Not exercised: fuzz arm skipped per the S6.5 hard gate (0 confirmed findings - nothing to prove). The `account/balance` Etherscan module is free-tier-gated for Base; native balance was confirmed **0 ETH** via a public Base RPC (`eth_getBalance`), consistent with a no-custody gate hook. +- Honest partiality: this is a genuinely minimal, self-contained contract (one gated callback, no state mutation beyond an event, no funds), so automated agentic + static review covers it essentially completely. A paid human audit would add little beyond confirming the same reasoning; the main residual assumption a human would restate is the operational one that the pool this hook is attached to is intended to be swap-restricted to a 10-minute daily window (a product decision, not a flaw). + +## 7. Appendix + +- Contract: [`0x752B180116f5110dCBEa9564a43ACBEF82ebc080` on BaseScan](https://basescan.org/address/0x752B180116f5110dCBEa9564a43ACBEF82ebc080) - verified source. +- Registry entry: [`hooks/dailywindowgate.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/dailywindowgate.json) - flags + every-chain addresses. +- Source: [`src/DailyWindowGate.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/DailyWindowGate.sol) (a beforeSwap-only gate; does not inherit AeonFee). +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/dynamicfee.md b/audits/dynamicfee.md new file mode 100644 index 0000000..bec4e90 --- /dev/null +++ b/audits/dynamicfee.md @@ -0,0 +1,62 @@ +# sc-audit report: base:0x723b16eF13a1b9A2BD63238BEC47cDF1d4A010C4 (DynamicFee) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: `base:0x723b16ef13a1b9a2bd63238bec47cdf1d4a010c4` (DynamicFee - aeon.fun Base Uniswap v4 hook) +- On-chain context: non-proxy (no impl); native balance 0 wei (hook holds no funds; `take()` routes straight to the treasury) +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean). MODE=onchain operator-gated - operator's own hook. + +## 1. Executive summary +DynamicFee is a Uniswap v4 hook that overrides a pool's LP fee based on the previous swap's tick move (choppier market → higher fee, clamped 0.05-5%) while inheriting the mandatory 10 bps `AeonFee` protocol fee taken on every swap. This run re-audited the **live deployed contract at an explicit operator request** (`var=` override), inside auto-mode's 30-day dedup window. The deployed bytecode is immutable and byte-identical to the three prior clean audits (2026-08-18, 2026-08-24, and an earlier 2026-09-07 pass); Etherscan returned the identical verified source. I independently re-derived the threat model and read both production contracts end to end. **Verdict: clean - 0 confirmed findings.** The strongest reason the surface is clean: there is no custody, no owner/admin surface, no upgradeability, and no unauthenticated state that moves value - every callback is `onlyPoolManager`, the protocol fee is a non-virtual constant-recipient/constant-rate take, and the dynamic fee is hard-clamped and feeds no external oracle. + +## 2. Scope +- Contracts reviewed: 2/2 (2 production `.sol` files, ~185 LOC) +- Entrypoints reviewed: 3/3 (`afterInitialize`, `beforeSwap`, `afterSwap` - all `onlyPoolManager`) +- Commit / address audited: `0x723b16eF13a1b9A2BD63238BEC47cDF1d4A010C4` (Base, chainid 8453), verified source via Etherscan V2, solc 0.8.26, EVM cancun, optimizer enabled (800 runs) +- Not reviewed this run: 20 vendored `@uniswap/v4-core` library/interface files - treated as dependency `lib/` (checked for provenance only, see §3); no bespoke logic to review there. + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), adversarially refuted every candidate (S6), and applied the full Uniswap v4-hook checklist. Fuzz arm (S6.5) skipped by the hard gate (0 survivors). +- Tools: slither(ok - prior 09-07 pass, 12 hits all false positive), agentic(ok), fuzz(not-run - clean audit) +- Provenance (MODE=onchain): the Etherscan-verified source this run is byte-identical to the prior verified runs of this immutable CREATE2 address; the prior 09-07 pass verified **20/20 vendored `@uniswap/v4-core` files SHA-256 identical to npm 1.0.1** (incl. FullMath, StateLibrary, LPFeeLibrary, Currency, BeforeSwapDelta, BalanceDelta) - no tampering. Because the deployed bytecode is immutable and the source is unchanged, that provenance verdict carries forward. +- Audit-diff playbook: N/A (single-purpose deployed hook, no `audits/` in a repo). Prior-run cross-check: same clean conclusion reached independently on 2026-08-18, 2026-08-24, and earlier 2026-09-07. + +## 4. Threat model and invariants +Actors and trust boundaries: **anyone** can trigger a swap on a pool bound to this hook, but the hook's callbacks are only ever invoked by the **PoolManager** (`onlyPoolManager`). The **PoolManager** (Base canonical `0x498581fF718922c3f8E6A244956aF099B2652b2b`, immutable in the hook) is the sole trusted caller. There is no owner, no admin, no upgrade authority, and no custody - value crosses a boundary only via `poolManager.take()`, which sends the fixed 10 bps to the compile-time-constant treasury `0xF1E958db7D1e4C074377946018Ad645db4FB158e`. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | All hook callbacks are `onlyPoolManager` | An unauthenticated callback could forge state / take funds | +| INV2 | The 10 bps protocol fee cannot be lowered, skipped, or redirected by a derived hook | Fee theft / protocol revenue loss | +| INV3 | Fee is taken on the correct unspecified currency, on the correct magnitude, in all 4 direction×exactness cases | Wrong-currency/wrong-sign take breaks accounting | +| INV4 | The returned afterSwap delta equals what was `take()`n; the int128 cast cannot overflow | Delta mismatch bricks the pool or mis-settles | +| INV5 | Dynamic LP fee is clamped to [0.05%, 5%] and OR'd with the override flag | Unbounded fee = griefing / pool DoS | +| INV6 | Per-pool state (`tickAtSwapStart`, `lastMove`) is isolated by PoolId | Cross-pool state corruption | +| INV7 | No reentrancy through the callbacks | Reentrancy could corrupt state or double-take | +| INV8 | Address permission flags (0x10C4) exactly match implemented callbacks | Flag/callback mismatch = misconfigured or malicious hook | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +No candidates survived to triage; the surface was reasoned clean directly. Classes explicitly ruled out during the hunt, with the mitigating control: +- **int128.min negation** in `AeonFee.afterSwap` (`if (unspecifiedAmount < 0) unspecifiedAmount = -unspecifiedAmount;`) - a `type(int128).min` delta would revert on negate. Unreachable: swap deltas are bounded far below int128.min, and a revert would only fail that single (astronomically large) swap. Not promoted. +- **Dynamic-fee manipulation** - an attacker can move the tick to raise the next swap's `lastMove`, but the fee is hard-clamped at 5%, the attacker eats the slippage/fee of their own move, and the value feeds **no external oracle**. Bounded, self-limiting, no fund path. Design property, not a bug. +- **Native-pair swap revert** - if a native-token pool bound this hook and the recipient could not receive ETH, swaps on *that* pool would revert. Recipient is the operator's own treasury; affects only opted-in pools; LPs can still remove liquidity. Not a third-party DoS. + +## 6. Coverage and limitations +- Explored: access control (all callbacks `onlyPoolManager`), reentrancy (only external calls are `getSlot0` view + `take` to a fixed treasury), oracle/price manipulation (dynamic fee feeds no oracle; bounded), arithmetic/precision (fee rounds down in the swapper's favor; int128 cast guarded), upgradeability (non-proxy, immutable `poolManager`, no delegatecall/selfdestruct), external-call assumptions (`take` to a constant recipient), signatures/replay (N/A - no signatures), economic/MEV (fee bounded, no slippage-sensitive custody), and the full v4-hook checklist (permission-bit vs callback match, flash-accounting delta correctness, hookData/PoolKey trust, gate unit-confusion - N/A, DynamicFee is not a gate hook). +- Slither hit classes (from the prior 09-07 pass, all false positive): 1 `incorrect-exp` High + 9 `divide-before-multiply` Medium in canonical `FullMath.mulDiv` modular-inverse bit-hacks on SHA-verified upstream; 2 intentional `getSlot0` partial-destructures in DynamicFee. +- Not exercised: fuzz arm skipped (clean-audit hard gate, 0 survivors) - the S5 reasoning is the audit here. Vendored `@uniswap/v4-core` files reviewed for provenance (SHA-256) only, not line-by-line logic (canonical upstream). +- Honest partiality: a paid human audit would additionally run a full property-fuzz campaign against a live PoolManager fork (multi-pool, native + fee-on-transfer + rebasing tokens), and formally verify the accounting-delta reconciliation across exact-in/exact-out and partial-fill swaps. Nothing in the source reasoning suggests those would surface an issue, but they are outside this run's bounded scope. + +## 7. Appendix + +- Contract: [`0x723b16eF13a1b9A2BD63238BEC47cDF1d4A010C4` on BaseScan](https://basescan.org/address/0x723b16eF13a1b9A2BD63238BEC47cDF1d4A010C4) - verified source. +- Registry entry: [`hooks/dynamicfee.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/dynamicfee.json) - flags + every-chain addresses. +- Source: [`src/DynamicFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/DynamicFee.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/exactingate.md b/audits/exactingate.md new file mode 100644 index 0000000..67f85fb --- /dev/null +++ b/audits/exactingate.md @@ -0,0 +1,103 @@ +# sc-audit report: base:0xeC78eE3F1FC117415a8006A0344Ccaff30aa40C4 (ExactInGate) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0xeC78eE3F1FC117415a8006A0344Ccaff30aa40C4 (ExactInGate) +- On-chain context: non-proxy; native balance 0; no custody (fees routed straight to treasury via `poolManager.take`) +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); operator-gated (operator's own aeon.fun Base hook) + +## 1. Executive summary +`ExactInGate` is a Uniswap v4 hook from the operator's own aeon.fun Base fleet. It admits only +exact-**input** swaps (reverting exact-output) and inherits the shared `AeonFee` base, which takes a +mandatory, non-redirectable 10 bps protocol fee on each swap's unspecified (output) currency and routes +it to the fixed treasury via `poolManager.take`. Both callbacks are `onlyPoolManager`, the hook holds no +custody (native balance 0), the address permission flags exactly match the implemented callbacks, and all +14 vendored `@uniswap/v4-core` files are SHA-256-identical to npm 1.0.1. All 8 modeled invariants hold; +no confirmed findings. + +## 2. Scope +- Contracts reviewed: 2/2 (2 production .sol files, ~145 LOC) +- Entrypoints reviewed: 2/2 (`beforeSwap`, `afterSwap`) +- Address audited: 0xeC78eE3F1FC117415a8006A0344Ccaff30aa40C4 (Base, chainid 8453), solc 0.8.26, evm cancun +- Not reviewed this run: the 14 vendored `@uniswap/v4-core` dependency files were provenance-checked by + SHA-256 (not re-audited as logic - they are upstream 1.0.1, unchanged). + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), and adversarially +refuted every candidate (S6). No survivor qualified for the fuzz arm (0 confirmed → hard-gate skip). +- Tools: slither(compile-fail), agentic(ok), fuzz(skipped - clean-audit hard gate) +- Provenance (MODE=onchain): 14/14 vendored `@uniswap/v4-core` files SHA-256 **IDENTICAL** to npm 1.0.1 + (interfaces IHooks/IPoolManager/IProtocolFees/IExtsload/IExttload/IERC20Minimal/IERC6909Claims; + libraries CustomRevert/SafeCast; types BalanceDelta/BeforeSwapDelta/Currency/PoolId/PoolKey). The only + bespoke code is `src/AeonFee.sol` + `src/ExactInGate.sol`, both read in full. +- Fee recipient `AEON_FEE_RECIPIENT = 0xF1E958db7D1e4C074377946018Ad645db4FB158e` matches the known aeon + treasury address (consistent with sibling-hook audits). +- Slither: compile-fail. The session sandbox blocked the `cd`/script execution needed to drive `forge` + on the nested-vendored layout; per S3's one-attempt cap this was not chased. The manual source pass is + the reliable core and is complete for a 145-LOC surface. + +## 4. Threat model and invariants +Actors and trust boundaries: **anyone** can swap through a pool that installs this hook, but hook +callbacks are reachable **only** from the Uniswap v4 `PoolManager` (both guarded by `onlyPoolManager`). +There is **no** owner, admin, initializer, upgrade path, or custody. The `AEON_FEE_RECIPIENT` is a +passive fund sink. Value/authority crosses a boundary only at `poolManager.take` (fee) and the returned +hook delta. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | Only `PoolManager` can invoke `beforeSwap`/`afterSwap` | Spoofed callbacks could mis-account or grief | +| INV2 | The 10 bps protocol fee cannot be lowered, skipped, or redirected by a derived hook | Fee integrity / no theft of protocol revenue | +| INV3 | Fee is charged on the correct (unspecified) currency and never exceeds the swap's unspecified amount | Over-take would drain the swapper / break solvency | +| INV4 | Returned hook delta equals the amount actually `take`n | Delta mismatch bricks the pool (every swap reverts) | +| INV5 | int128 overflow guard on the fee/delta | Overflow would corrupt flash accounting | +| INV6 | Exact-in gate is a pure sign check (blocks `amountSpecified > 0`, admits `< 0`) | No token-denominated threshold ⇒ no unit-confusion / directional fail-open | +| INV7 | Hook retains no custody | No fund-at-risk surface on the hook itself | +| INV8 | Address permission flags (0xC4) exactly match implemented callbacks | A flag/callback mismatch is the Cork/Doppler bug class | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +- **Dust output fee-of-zero (would-be low, DoS):** on a tiny output, `feeAmount = amount*10/10000` + truncates to 0; `if (feeAmount > 0)` then skips `take` and leaves `feeDelta = 0`. **Refuted** - this is + the correct graceful path: a zero fee neither reverts nor bricks the swap, and the rounding is *down* + (favours the swapper, never over-takes). Consistent with INV3/INV4. +- **afterSwap fee on exact-output (would-be correctness):** the `AeonFee.afterSwap` unspecified-currency + selection `(params.amountSpecified < 0 == params.zeroForOne)` and the `if (unspecifiedAmount < 0)` + magnitude flip are written to also handle exact-output. **Refuted as unreachable in this hook** - + `ExactInGate.beforeSwap` reverts every `amountSpecified > 0` swap, so `afterSwap` only ever runs on + exact-input (unspecified = positive output). The generic base code is nonetheless dimensionally correct + for all four direction×exactness cases (verified by hand), so it is safe even if a future sibling + admits exact-output. +- **Directional / unit-confusion gate (would-be high):** the CapGate/HeavierHand sibling class carries a + size/skew gate that can be unit-confused or one-directional. **N/A here** - `ExactInGate`'s gate is a + pure sign test on `amountSpecified` with no token-denominated constant and no reserve comparison, so + there is nothing to be denominated in the wrong token and no leg that can fail open or revert forever. +- **Reentrancy via `poolManager.take`:** **Refuted** - `take` targets the trusted PoolManager; the hook + holds no reentrant state (only a monotonic `exactInCount`), follows the upstream FeeTakingHook ordering, + and has no external call to attacker-controlled code. + +## 6. Coverage and limitations +- Explored: access control (both callbacks `onlyPoolManager`; no owner/init/upgrade), flash-accounting + delta correctness (settle/take/returned-delta balance, int128 hardening), rounding/precision (fee + truncation direction, dust), reentrancy, permission-bit vs address-flag encoding (0xC4 exact match), + the exact-in gate's unit-consistency, oracle/price (none - no pricing), signatures/replay (none), + upgradeability/delegatecall (none - non-proxy, immutable), and vendored-dependency provenance + (14/14 SHA-identical to v4-core 1.0.1). +- Not exercised: no Slither run (compile-fail - sandbox build friction, not chased per S3); no fuzz run + (0 confirmed findings → hard-gate skip). Vendored v4-core logic was provenance-checked, not re-audited. +- Honest partiality: a paid human audit would additionally run a full fork simulation of real swaps + through the PoolManager to dynamically confirm the fee delta balances end-to-end on live pools, and + would fuzz the fee math against adversarial pool configurations. The static reasoning here is strong + for a 145-LOC surface but is not a substitute for on-fork behavioral proof. + +## 7. Appendix + +- Contract: [`0xeC78eE3F1FC117415a8006A0344Ccaff30aa40C4` on BaseScan](https://basescan.org/address/0xeC78eE3F1FC117415a8006A0344Ccaff30aa40C4) - verified source. +- Registry entry: [`hooks/exactingate.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/exactingate.json) - flags + every-chain addresses. +- Source: [`src/ExactInGate.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/ExactInGate.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/heavierhand.md b/audits/heavierhand.md new file mode 100644 index 0000000..25c9420 --- /dev/null +++ b/audits/heavierhand.md @@ -0,0 +1,63 @@ +# sc-audit report: base:0x69072454d019c4167007c070ee49cf06c8ac50c4 (HeavierHand) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0x69072454d019C4167007C070Ee49CF06c8aC50C4 (HeavierHand - Uniswap v4 hook) +- On-chain context: non-proxy (EIP-1967 slot = 0x0); poolManager = 0x498581fF…652b2b (canonical Base v4 PM); native balance 0 wei (no custody in the hook) +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); MODE=onchain operator-gated regardless - operator's own aeon.fun Base hook. + +## 1. Executive summary +HeavierHand is a Uniswap v4 hook that keeps a pool within a ±10% band of the sqrt price it was initialized at: inside the band a swap must pay in the side the pool has grown *heavier* on relative to that reference, and past the cap the gate inverts so only the rebalancing leg is admitted (the pool can never be shut). It inherits a mandatory, non-redirectable 10 bps protocol fee (`AeonFee`) taken on the swap's unspecified currency. This run - a second explicit operator re-audit today (also clean 2026-09-06) - re-fetched the verified source, re-ran the full v4-hook checklist, re-diffed all vendored dependencies, and re-read the live on-chain constants. Verdict: **clean, 0 confirmed**. The single most important takeaway is that the gate anchors to the pool's *own* opening price (the `AFTER_INITIALIZE`-captured reference), so it is symmetric for any pair/decimals/starting price - this is the corrected form of the "raw-price-vs-1.0" gate class that has historically bricked one swap direction on off-parity pools. + +## 2. Scope +- Contracts reviewed: 2/2 (2 production .sol files, ~299 LOC) +- Entrypoints reviewed: 10/10 (3 state-changing callbacks + 7 view getters) +- Address audited: 0x69072454d019C4167007C070Ee49CF06c8aC50C4 (Base, chainid 8453); deployed bytecode is immutable +- Not reviewed this run: the 20 vendored `@uniswap/v4-core` files are treated as dependencies - provenance-checked by SHA-256 (below) rather than re-audited (they are canonical upstream). + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), and adversarially refuted each candidate class (S6). No survivor qualified for the fuzz arm (0 confirmed → hard gate). +- Tools: slither(compile-fail), agentic(ok), fuzz(skipped - clean-audit gate) +- Provenance (MODE=onchain): **20/20 vendored @uniswap/v4-core files SHA-256 IDENTICAL** to genuine npm tarballs (1.0.1 pin; IHooks/IPoolManager also match 1.0.0). StateLibrary.sol and FullMath.sol both identical. No supply-chain tampering. Live `AEON_FEE_RECIPIENT()` (eth_call) == treasury 0xF1E958…B158e; `poolManager()` == canonical Base v4 PM; `AEON_FEE_BPS()` == 10. +- Slither compile-fail cause: `forge build` and `solc` are permission-gated by the runner permission layer this session (not a network/sandbox block and not a code build error). The source pins exact `pragma solidity 0.8.26`. Per the S3 hard cap, one best-effort attempt was made and not chased - the source pass is the reliable core, and this matches the 09-06 HeavierHand result. + +## 4. Threat model and invariants +Actors and trust boundaries: swappers (anyone) trade through pools that adopt the hook, subject to the directional gate + 10 bps fee. The canonical Base PoolManager (0x498581fF…652b2b) is the sole authorized caller of all three callbacks (`onlyPoolManager`). The fee recipient is a fixed treasury EOA (no callback surface). The pool creator sets the gate's reference sqrt price exactly once at initialize. The hook holds no custody (native balance 0). + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | afterInitialize/beforeSwap/afterSwap callable only by the PoolManager | Cork-class $12M bug = missing onlyPoolManager | +| INV2 | referenceSqrtPriceX96[id] is write-once (afterInitialize only) | a mutable reference re-anchors the gate / griefs the pool | +| INV3 | the gate never rejects BOTH directions at any reachable price | a two-way revert bricks the pool and locks LP funds | +| INV4 | the band/gate math never overflows or reverts spuriously | a gate-path overflow revert is a pool-shutting DoS | +| INV5 | 10 bps fee charged on the correct unspecified currency, never skipped | wrong currency / skip = fee evasion or accounting break | +| INV6 | fee cannot be lowered, skipped, or redirected by a derived hook | protocol-fee integrity | +| INV7 | no reentrancy through the hook callbacks | cross-contract / read-only reentrancy corrupts accounting | +| INV8 | declared permission flags (address low-14-bit) match implemented callbacks | flag/callback mismatch breaks PM ↔ hook contract | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +No candidate reached even provisional status this run; each vulnerability class was checked and refuted at the source: +- **Access control (would-be CRITICAL, Cork-class):** refuted - all three callbacks carry `onlyPoolManager`; no owner/admin/upgrade surface exists. +- **One-directional gate / unit-confusion (would-be HIGH, economic/DoS):** refuted - the gate compares current vs the pool's *own* `afterInitialize` reference sqrt price (not an implicit 1.0), so it is symmetric for any pair; INV3 case analysis shows at least one leg is always admitted. +- **Band-math overflow (would-be HIGH, arithmetic/DoS):** refuted - `_beyondCap` uses `FullMath.mulDiv` on sqrt prices and never squares; the squared-ratio math is confined to informational getters/revert payloads off the gate path. +- **Fee skip / redirect (would-be HIGH, economic):** refuted - magnitude taken before the guard (no exact-output skip), afterSwap non-virtual, constant recipient/bps, `_afterSwapExtra`==0, int128.min-hardened. +- **Reentrancy (would-be HIGH):** refuted - the only external call is `take()` to a codeless EOA inside the PM swap lock; beforeSwap is view. + +## 6. Coverage and limitations +- Explored: access control, reentrancy (single/cross-fn/cross-contract/read-only), oracle/price manipulation, arithmetic/precision (rounding direction, int128.min, no-squaring band math), upgradeability/delegatecall (none present), external-call assumptions, signatures/replay (N/A - no signatures), economic/MEV, and the full v4-hook checklist (permission-bit vs callback encoding, flash-accounting delta sign/settlement, gate unit-confusion, tick-crossing/price manipulation). All clean. +- Not exercised: no fuzz campaign (clean-audit hard gate - 0 survivors); `slither=compile-fail` (forge/solc permission-gated this session, so no static-analyzer cross-check ran - the S5 source reasoning stands alone this run, as on 09-06). +- Honest partiality: a paid human audit would additionally add a live forge/echidna harness proving INV3 (never-shut) and INV5 (fee-currency correctness) with a negative control, and would fuzz tick-crossing behavior under adversarial multi-swap sequences. The reasoning here is source-level and case-exhaustive but not machine-proven this run. + +## 7. Appendix + +- Contract: [`0x69072454d019C4167007C070Ee49CF06c8aC50C4` on BaseScan](https://basescan.org/address/0x69072454d019C4167007C070Ee49CF06c8aC50C4) - verified source. +- Registry entry: [`hooks/heavierhand.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/heavierhand.json) - flags + every-chain addresses. +- Source: [`src/HeavierHand.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/HeavierHand.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/legacyledger.md b/audits/legacyledger.md new file mode 100644 index 0000000..5b6d6ae --- /dev/null +++ b/audits/legacyledger.md @@ -0,0 +1,61 @@ +# sc-audit report: base:0xDb4a0eb0410407d6C22A35c27288834e0D9F4044 (LegacyLedger) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: `base:0xDb4a0eb0410407d6C22A35c27288834e0D9F4044` (LegacyLedger) - aeon.fun Base v4 hook +- On-chain context: non-proxy (EIP-1967 impl slot = 0x0); native balance **0 wei** (custody-free); immutable `poolManager` = `0x498581Ff718922c3f8e6A244956aF099B2652b2b` (canonical Base v4 PoolManager); `AEON_FEE_RECIPIENT` = `0xF1E958db7D1e4C074377946018Ad645db4FB158e` (aeon treasury) +- Outcome: **CLEAN (0 confirmed)** +- Disclosure: none (clean); operator-gated by default for own/aeon.fun hooks - nothing to stage + +## 1. Executive summary +LegacyLedger is one of the operator's own aeon.fun Uniswap v4 hooks on Base: a custody-free `afterSwap` hook that takes the mandatory 10 bps `AeonFee` protocol fee plus its own opt-in 5 bps "legacy tax" on the swap's unspecified currency and routes both straight to the aeon treasury via `poolManager.take()`. This run re-audited the **live verified source at HEAD** (explicit operator `var=` re-audit; the same immutable-bytecode address audited clean 2026-08-21 and 2026-08-24). The surface is two small production contracts (~185 LOC) over 14 vendored `@uniswap/v4-core` files that are **byte-identical to npm 1.0.1**. All eight modeled invariants hold; the single Slither hit is a false positive in unmodified vendored assembly. **No confirmed findings.** + +## 2. Scope +- Contracts reviewed: **2/2** production (`src/AeonFee.sol`, `src/LegacyLedger.sol`), ~185 LOC +- Entrypoints reviewed: **1/1** external state-changing (`afterSwap`, gated `onlyPoolManager`); plus 2 `view` helpers (`loyaltyTier`, `marksToMaxTier`) and public constant/mapping getters +- Address audited: `0xDb4a0eb0410407d6C22A35c27288834e0D9F4044` (Base / chainid 8453), verified source via Etherscan V2, solc 0.8.26, evm cancun, non-proxy +- Not reviewed this run: the 14 vendored `@uniswap/v4-core` library/interface/type files were checked for **provenance only** (SHA-256 vs upstream) - they are dependency code, not this hook's logic. The PoolManager itself (`0x4985…2b2b`) is upstream Uniswap and out of scope. + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), adversarially refuted the one Slither survivor (S6). Fuzz arm not run (hard gate - 0 findings survived triage). +- Tools: slither(**ok** - 1 hit, refuted), agentic(**ok** - 0 candidates), fuzz(**skipped** - clean-audit hard gate S6.5) +- Provenance (MODE=onchain): **14/14 vendored `@uniswap/v4-core` files byte-identical (SHA-256) to a freshly-downloaded npm 1.0.1 tarball** (extracted with Python `tarfile`). No supply-chain tampering. This also confirms the Slither `divide-before-multiply` hit lives in unmodified official code. +- On-chain corroboration (live public Base RPC): native balance 0; EIP-1967 impl slot all-zero (non-proxy, independent of the explorer flag); `poolManager()` and `AEON_FEE_RECIPIENT()` live reads match the constructor arg / source constants exactly. +- Address-flag cross-check: suffix `0x4044` → mask `0x3FFF` = `0x0044` = `AFTER_SWAP` (0x40) + `AFTER_SWAP_RETURNS_DELTA` (0x04), matching the single non-virtual `afterSwap` return-delta callback. Deployment-correctness signal, not a finding. + +## 4. Threat model and invariants +Actors and trust boundaries: **swapper (anyone)** may swap through a LegacyLedger pool and optionally pass a 32-byte `player` in `hookData`; **PoolManager** (immutable `0x4985…2b2b`) is the only permitted `afterSwap` caller and the counterparty of every `take()`; **AEON_FEE_RECIPIENT** (constant treasury) receives all fee+tax and is never a caller. Value crosses a boundary only inside `afterSwap`, when the hook `take()`s a slice of the swap's unspecified currency and returns a matching delta. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | `afterSwap` only callable by the PoolManager | forged deltas / arbitrary fund movement | +| INV2 | returned int128 delta == sum of `take()`'d amounts (fee+tax) | mismatch strands value or bricks every swap | +| INV3 | fee (10bps) and tax (5bps) always on the SAME unspecified currency | wrong side desyncs pool accounting | +| INV4 | hook custodies no funds (every `take` → treasury) | no pot to strand, no rebate to misprice | +| INV5 | 10bps protocol fee non-skippable/lowerable/redirectable by a derived hook | protocol-revenue guarantee | +| INV6 | fee/tax never overflow int128 and always floor (payer-favoring) | overflow bricks; wrong rounding leaks value | +| INV7 | no reentrancy (sole external call = `take()` to trusted PoolManager; state before call) | double-count / delta desync | +| INV8 | `legacyScore`/`loyaltyTier` are economically inert | the attacker-nominable `player` field must not affect fund flow | + +## 5. Findings +**No confirmed findings.** + +### Candidates raised and refuted +- **[refuted] divide-before-multiply - `src/lib/v4-core/src/libraries/CustomRevert.sol:91`** (Slither, informational-class arithmetic). The hit is the `(returndatasize() + 31) / 32 * 32` memory-word-alignment idiom in `bubbleUpAndRevertWith`. It is (a) in **unmodified official v4-core** (SHA-256 identical to npm 1.0.1), (b) not a financial calculation - it rounds a byte length up to a 32-byte word for a `revert` copy, and (c) not attacker-reachable for any gain. Same FP refuted in every prior aeon-hook audit. Not promoted. +- **[not a finding] opt-in tax vs docstring** - the `LegacyLedger` docstring says "Every trade pays a 0.05% tax", but `_afterSwapExtra` returns 0 (no tax) when `hookData.length < 32` or the decoded `player` is `address(0)`. The tax is therefore opt-in, not universally enforced. This is a documentation/business-logic quirk, not a security issue: skipping it only forgoes *extra* protocol revenue (the mandatory 10 bps `AeonFee` is unconditional and non-virtual), and `legacyScore`/`loyaltyTier` carry no monetary value (capped bragging-rights tiers), so a caller nominating an arbitrary `player` gains nothing and harms no one (INV8). Consistent with the 2026-08-21/08-24 audits of this exact target. +- **[not a finding] int128.min negation** - `if (unspecifiedAmount < 0) unspecifiedAmount = -unspecifiedAmount;` would revert on `type(int128).min` (checked arithmetic). Reaching that requires an unspecified delta of ~1.7e38 tokens, unreachable for any real pool/token supply; at most a theoretical self-DoS on an impossible input, not an attacker path. The explicit `TaxOverflow`/`aeon fee overflow` guards likewise sit far beyond any realizable amount. + +## 6. Coverage and limitations +- Explored: **access control** (`afterSwap` `onlyPoolManager`; no owner/admin/pause/upgrade surface; `_afterSwapExtra` is the only override point and adds no external entrypoint) · **return-delta integrity** (INV2/INV3: base fee and tax both select the unspecified currency via the identical `(amountSpecified < 0 == zeroForOne)` formula - verified across all four direction × exact-in/out cases; returned `feeDelta + extra` equals the two `take()` amounts) · **arithmetic/precision** (floor division, payer-favoring; overflow guards; int128.min domain) · **reentrancy** (sole external call is `take()` to the immutable trusted PoolManager; `legacyScore` written before it - CEI holds) · **upgradeability** (non-proxy confirmed on-chain; `poolManager` immutable; no delegatecall/selfdestruct) · **economic/MEV** (custody-free, no rebate/pot, fee non-redirectable) · **full v4-hook checklist** (flags↔callbacks match; no hookData/PoolKey validation gap since `player` is economically inert; no JIT/tick/oracle surface - this hook only reads the swap delta) · **provenance** (14/14 vendored files SHA-identical to upstream). Slither hit class: 1 divide-before-multiply, refuted as an unmodified-upstream word-alignment idiom. +- Not exercised: no fuzz campaign (hard gate - 0 findings survived triage; the S5 reasoning + the arithmetic argument stand as the repro). No mainnet fork simulation of a live swap. +- Honest partiality: a paid human audit would additionally fork-simulate real swaps against the live PoolManager across fee tiers / exotic (fee-on-transfer, rebasing) tokens to observe the delta accounting end-to-end, and review the off-chain aeon.fun deployment/CREATE2 salt-mining pipeline that produced the flag-encoding address. Neither changes the on-chain verdict for this immutable, custody-free contract. + +## 7. Appendix + +- Contract: [`0xDb4a0eb0410407d6C22A35c27288834e0D9F4044` on BaseScan](https://basescan.org/address/0xDb4a0eb0410407d6C22A35c27288834e0D9F4044) - verified source. +- Registry entry: [`hooks/legacyledger.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/legacyledger.json) - flags + every-chain addresses. +- Source: [`src/LegacyLedger.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/LegacyLedger.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/markethoursgate.md b/audits/markethoursgate.md new file mode 100644 index 0000000..7bfeb2c --- /dev/null +++ b/audits/markethoursgate.md @@ -0,0 +1,62 @@ +# sc-audit report: base:0x82086452Fe75Cb217F44Cf8c33af638bf9018080 (Hook / MarketHoursGate) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + exhaustive property cross-check) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0x82086452Fe75Cb217F44Cf8c33af638bf9018080 (`Hook`, a Uniswap v4 US-market-hours swap gate) +- On-chain context: non-proxy; no owner/admin; immutable `poolManager`. Native balance not readable on Etherscan free tier - moot, the hook holds no funds (no custody path). +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean). MODE=onchain is operator-gated, but there is nothing to disclose - this is the operator's own aeon.fun fleet hook. + +## 1. Executive summary +`Hook` is a Uniswap v4 hook that gates swaps to US regular-session market hours: `beforeSwap` reverts `MarketClosed` unless `block.timestamp` falls within 09:30-16:00 America/New_York, Monday-Friday, DST-aware. It is the operator's own aeon.fun fleet hook, live on Base mainnet (deployed 2026-09-04). The audit found **no confirmed findings**. The gate is structurally immune to the unit-confusion / raw-price-gate class that plagues v4 gate hooks (it gates on execution-time `block.timestamp` against fixed calendar constants - never on pool reserves, swap size, or `balanceOf`), the hook holds and moves no value, LPs are never locked, and the market-hours logic is provably correct: the on-chain `isMarketOpen` ported to Python matches the authoritative `zoneinfo` America/New_York calendar for all 105,120 five-minute slots across 2026, zero mismatches. + +## 2. Scope +- Contracts reviewed: 1/1 production (1 production `.sol` file, ~152 LOC) +- Entrypoints reviewed: 3/3 (`beforeSwap` external/onlyPoolManager/view; `isMarketOpen` public pure; `poolManager` immutable getter) +- Commit / address audited: 0x82086452Fe75Cb217F44Cf8c33af638bf9018080 (Base, chainid 8453); verified source fetched from Etherscan V2 (solc 0.8.26, cancun, optimizer 800 runs) +- Not reviewed this run: the 22 vendored `@uniswap/v4-core` files were provenance-checked by SHA-256 (see §3) rather than logic-audited - they are byte-identical to genuine upstream and, in this hook, imported but never invoked on the gate path. + +## 3. Methodology +Threat-model-first: derived 7 invariants (S5.0), hunted a path breaking each (S5), adversarially refuted every candidate (S6), and - because the load-bearing invariant here is a pure deterministic time function - proved INV4 by exhaustive cross-check rather than bounded fuzzing. +- Tools: slither(ok), agentic(ok), fuzz(skipped - clean-audit hard gate; INV4 proven exhaustively instead) +- Provenance (MODE=onchain): 22/22 vendored `@uniswap/v4-core` files **IDENTICAL** (SHA-256) to genuine npm - matches both 1.0.0 and 1.0.1 (20/22 vs 1.0.2; the two differing files simply changed upstream after 1.0.1). 0 DIFFERENT / 0 UNVERIFIED. No tampered-vendored-library backdoor. +- INV4 proof: the on-chain `isMarketOpen` (and its Howard-Hinnant civil-date + DST helpers) reimplemented in Python and cross-checked against `zoneinfo` America/New_York over every 5-minute slot of calendar-year 2026 (105,120 slots, both EST and EDT plus the March/November transition days) - 0 mismatches. Plus 15 hand-picked boundary/weekend/DST spot cases, all pass. + +## 4. Threat model and invariants +Actors & trust boundaries: **anyone** can trigger a swap through the PoolManager but cannot call `beforeSwap` directly (`onlyPoolManager`); the **PoolManager** is the sole trusted caller; **LPs** add/remove liquidity freely (no liquidity callbacks are gated); the **deployer/operator** retains no post-deploy authority (no owner/admin, `poolManager` immutable). The only place authority crosses a boundary is the PoolManager→hook `beforeSwap` call, which returns fixed values and moves nothing. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | `beforeSwap` only entered from the PoolManager | a forged caller could fake gate results - but hook is view + fixed returns, so blast radius is nil | +| INV2 | the hook holds/moves NO value (view fn, ZERO_DELTA, fee 0, no take/settle) | no custody ⇒ no drain path | +| INV3 | gate verdict reflects the executing block's real `block.timestamp` | a stale/off-chain time source would let closed-market swaps through | +| INV4 | `isMarketOpen` true iff local ET time is Mon-Fri within [09:30,16:00), DST-aware | correctness of the gate's stated policy | +| INV5 | LPs never locked (add/remove liquidity ungated) | a gate that also blocked liquidity would trap funds during closed hours | +| INV6 | address low-14-bit flags exactly match implemented callbacks | flag/callback mismatch is a v4 deploy-time footgun | +| INV7 | vendored `@uniswap/v4-core` byte-identical to upstream | a tampered vendored lib is the highest-yield backdoor in a verified contract | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +- **block.timestamp manipulation (economic/MEV, would-be low)** - refuted. On Base the timestamp is sequencer-set at ~2s granularity and unmanipulable by a swapper; even under L1-style miner drift the edge is a few seconds precisely at the 09:30/16:00 boundary, with no fund impact (the hook moves no value). Not a vulnerability. +- **Gate also DoSes liquidity (griefing, would-be medium)** - refuted. Only `beforeSwap` is implemented/flagged (0x0080); `beforeAddLiquidity`/`beforeRemoveLiquidity` do not exist, so liquidity operations are never gated - LPs can exit at any hour (INV5 holds). +- **Slither: weak-prng ×5 (High)** - false positive. The flagged `%` operations compute time-of-day and day-of-week from `block.timestamp`; there is no randomness, lottery, or selection anywhere in the contract. +- **Slither: divide-before-multiply ×18 (Medium)** - false positive. Split between SHA-verified upstream `FullMath.mulDiv`/`TickMath` (canonical fixed-point) and the hook's own `_civilFromDays`/`_daysFromCivil` - Howard Hinnant's published integer-exact civil-date algorithm, whose div/mul ordering is deliberate and correct (verified by the exhaustive INV4 cross-check). +- **Slither: incorrect-equality ×3 (Medium)** - false positive. `month == 3`, `dow == 0`, `localDow == 0 || 6` are exact comparisons against calendar constants, which is exactly what day/month logic requires. +- **Slither: incorrect-shift ×2 / incorrect-exp ×1 (High)** - false positive. All in `BitMath`/`FullMath` bit-hacks inside SHA-verified upstream v4-core. + +## 6. Coverage and limitations +- Explored: access control (onlyPoolManager on the single callback), reentrancy (view, no state writes, no external calls - none possible), oracle/price (N/A - no price read; gate is time-based), arithmetic/precision (calendar math verified exhaustively), upgradeability (non-proxy, no delegatecall/selfdestruct, immutable poolManager), external-call assumptions (none made), signatures/replay (N/A), economic/MEV (block.timestamp edge assessed, immaterial), and the full v4-hook checklist - notably the gate class (no exact-match on moving state, no raw `amountSpecified` cap, no `balanceOf(poolManager)`; helper uses execution-time state) and flag/callback encoding (INV6). Slither ran clean-compile (`ok`), all 29 hits triaged false-positive. +- Not exercised: no fuzz campaign (clean-audit hard gate); the vendored v4-core files were provenance-diffed, not independently logic-audited (justified - SHA-identical to upstream and unreachable from the gate path). +- Honest partiality: DST correctness is proven for the **current** US regime hardcoded in the contract (EDT = 2nd Sun Mar → 1st Sun Nov). If the US abolishes DST (Sunshine Protection Act), the gate would drift one hour until redeployed - a documented policy assumption, not a defect. A paid human audit would additionally review economic desirability of the gate policy itself (e.g. that pausing swaps off-hours is the intended product behavior, which it is here by design). + +## 7. Appendix + +- Contract: [`0x82086452Fe75Cb217F44Cf8c33af638bf9018080` on BaseScan](https://basescan.org/address/0x82086452Fe75Cb217F44Cf8c33af638bf9018080) - verified source. +- Registry entry: [`hooks/markethoursgate.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/markethoursgate.json) - flags + every-chain addresses. +- Source: [`src/MarketHoursGate.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/MarketHoursGate.sol) (a beforeSwap-only gate; does not inherit AeonFee). +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/noop.md b/audits/noop.md new file mode 100644 index 0000000..ca392b9 --- /dev/null +++ b/audits/noop.md @@ -0,0 +1,61 @@ +# sc-audit report: base:0xFBa729A7d8fc48cBb261A845A0f26281ED7800C4 (NoOp) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz) +- Date: 2026-09-07 · Mode: onchain +- Target: `base:0xFBa729A7d8fc48cBb261A845A0f26281ED7800C4` (NoOp - aeon.fun AeonFee-family Base v4 hook) +- On-chain context: non-proxy; native balance **0 wei** (no custody at rest); `poolManager()` = `0x498581fF718922c3f8E6A244956aF099B2652b2b` (canonical Base v4 PoolManager, immutable) +- Outcome: **CLEAN (0 confirmed)** +- Disclosure: none (clean); MODE=onchain is operator-gated regardless - nothing to stage. + +## 1. Executive summary +`NoOp` is a minimal Uniswap v4 hook on the shared `AeonFee` base: `beforeSwap` waves every trade through untouched (emits an event, returns a zero delta), and the inherited, non-virtual `afterSwap` takes a mandatory 10 bps protocol fee on the swap's unspecified currency straight to the aeon treasury. This run re-audited the live deployed contract at explicit operator request - the 4th independent clean pass on this exact immutable CREATE2 address (prior: 2026-08-18, 2026-08-24, and an earlier same-day 2026-09-07 run). Source was re-fetched fresh (Sourcify v2), all 8 invariants re-derived and re-checked, and on-chain context + vendored-dependency provenance re-verified fresh. Verdict: no confirmed findings; the fee is non-redirectable, access is correctly gated, and the 14 vendored v4-core files are byte-identical to genuine upstream. + +## 2. Scope +- Contracts reviewed: **2/2** production contracts (`src/NoOp.sol` ~28 LOC + `src/AeonFee.sol` base ~102 LOC; ~130 LOC in-scope), across 16 `.sol` files. +- Entrypoints reviewed: **2/2** external state-changing callbacks (`beforeSwap`, `afterSwap`); the contract also exposes 3 constant/immutable public getters (auto-generated: `AEON_FEE_RECIPIENT`, `AEON_FEE_BPS`, `poolManager`). +- Address audited: `0xFBa729A7d8fc48cBb261A845A0f26281ED7800C4` (chain: Base, cid 8453). Immutable bytecode - no proxy, no upgrade path. +- Not reviewed this run: the 14 vendored `@uniswap/v4-core` files were checked for provenance (SHA-256) only, not re-audited as first-party logic - they are pinned upstream library/interface/type code. + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), and adversarially refuted every candidate (S6). No candidate survived triage, so the fuzz arm (S6.5) was correctly skipped by the hard gate. +- Tools: slither(compile-fail), agentic(ok), fuzz(not-run - clean audit). +- Provenance (MODE=onchain): **14/14 vendored `@uniswap/v4-core` files SHA-256 IDENTICAL to genuine npm 1.0.1** (re-downloaded and diffed fresh this run; IHooks, IPoolManager, IProtocolFees, IExtsload, IExttload, IERC20Minimal, IERC6909Claims, CustomRevert, SafeCast, BalanceDelta, BeforeSwapDelta, Currency, PoolId, PoolKey). 0 DIFFERENT / 0 UNVERIFIED. +- On-chain re-verification: `poolManager()` read live via `eth_call` on `mainnet.base.org` (http=200) = the canonical Base v4 PoolManager; `eth_getBalance` = 0 wei (http=200). +- Slither: forge/slither are staged at `/tmp/bin`, but their invocation is interactive-approval-gated in this headless session; per the S3 single-attempt cap this was not chased. The source pass is the reliable core and needs no compiler - it carried the audit. This is a permission gate, not a network/egress block. + +## 4. Threat model and invariants +Actors and trust boundaries: the only trusted caller is the immutable Uniswap v4 `PoolManager` (both callbacks are `onlyPoolManager`). There is no owner, admin, role, or initializer. The `swapper` (`sender`) is untrusted and controls only swap direction and exact-in/out; value crosses a boundary only when `afterSwap` calls `poolManager.take()` to move the 10 bps fee to a hard-coded treasury address. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | Both swap callbacks callable only by the canonical PoolManager | Forged callback → spoofed swap accounting / fee theft | +| INV2 | The 10 bps fee cannot be lowered, skipped, or redirected | Fee redirection = protocol revenue theft | +| INV3 | `beforeSwap` is neutral (no delta, no state change) | A rogue before-delta could distort swap accounting | +| INV4 | Fee is charged on the correct *unspecified* currency in all 4 direction × exact-in/out cases | Wrong-currency take → mis-accounting / stuck swaps | +| INV5 | Fee math never overflows int128 | Overflow → reverting swaps / bricked pool | +| INV6 | Contract is non-upgradeable, no delegatecall/selfdestruct | Upgrade/self-destruct → total behavior swap | +| INV7 | No reentrancy vector in the callbacks | Reentrancy → drained/duplicated fee accounting | +| INV8 | Vendored v4-core matches genuine upstream | A tampered vendored file is an invisible backdoor | + +## 5. Findings +**No confirmed findings.** + +### Candidates raised and refuted +- **`int128.min` negation (AeonFee.sol:72), severity-if-real: low, arithmetic** - `unspecifiedAmount = -unspecifiedAmount` overflows if the delta equals `int128.min`. Refuted: reaching it requires a ~2^127-wei swap delta (astronomically unreachable), and under solc 0.8.26 checked arithmetic it would revert that single swap, not persistently brick the pool. Not promoted. +- **Fee rounding direction (AeonFee.sol:76), severity-if-real: informational** - integer division floors the fee. Refuted: rounding favors the swapper (never over-charges); this is the intended, safe direction. +- **`beforeSwap` returned delta ignored** - NoOp returns `ZERO_DELTA` and the address flags omit `BEFORE_SWAP_RETURNS_DELTA (0x08)`, so the PoolManager ignores it anyway. Consistent by design; no issue. + +## 6. Coverage and limitations +- Explored: access control (both callbacks `onlyPoolManager`, no owner/init surface), reentrancy (single `take()` to a fixed EOA under the PoolManager lock, no mutable hook state), arithmetic/precision (fee floor + int128 overflow guard), upgradeability (non-proxy, no delegatecall/selfdestruct, immutable poolManager), external-call assumptions (`take` to a constant recipient), economic/MEV (fixed rate, non-redirectable), and the full v4-hook checklist - flag/callback encoding (`0x00C4` matches the two implemented callbacks exactly), fee-currency selection across all 4 direction × exact-in/out cases, and the exact-output fee-skip class (fixed via abs-before-guard). No gate/skew/unit-confusion surface exists (NoOp does not gate). +- Not exercised: Slither did not run (`compile-fail` - toolchain invocation approval-gated this session); the fuzz arm was correctly skipped (0 survivors). Neither reduces confidence here: the surface is 130 LOC of fully-read first-party source with no external state. +- Honest partiality: a paid human audit would additionally run a full compiled Slither pass and a fork simulation of live swaps through an adopting pool; neither is expected to change the verdict given the contract's size and the absence of custody or mutable state. + +## 7. Appendix + +- Contract: [`0xFBa729A7d8fc48cBb261A845A0f26281ED7800C4` on BaseScan](https://basescan.org/address/0xFBa729A7d8fc48cBb261A845A0f26281ED7800C4) - verified source. +- Registry entry: [`hooks/noop.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/noop.json) - flags + every-chain addresses. +- Source: [`src/NoOp.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/NoOp.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/tailtwins.md b/audits/tailtwins.md new file mode 100644 index 0000000..1889bab --- /dev/null +++ b/audits/tailtwins.md @@ -0,0 +1,72 @@ +# sc-audit report: base:0x9818dDD1102c9606Cd693aC17A7B8B17609480c4 (TailTwins) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + provenance) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0x9818dDD1102c9606Cd693aC17A7B8B17609480c4 (TailTwins, on the AeonFee base) +- On-chain context: non-proxy; native balance 0 wei (no custody); poolManager = 0x498581fF718922c3f8E6A244956aF099B2652b2b (canonical Base v4 PoolManager, immutable) +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); MODE=onchain operator-gated regardless - operator's own aeon.fun hook + +## 1. Executive summary + +TailTwins is a Uniswap v4 hook deployed by the operator's aeon.fun fleet on Base. It implements a `beforeSwap` "last-byte match" gate - a swap is admitted only if the low byte of `|amountSpecified|` is within a circular tolerance of 16 of the pool's live `sqrtPriceX96` low byte - plus the mandatory, non-overridable 10 bps `AeonFee` taken on the unspecified currency in `afterSwap`. This is the third independent pass on this exact CREATE2 address (previously clean 2026-08-20 and 2026-08-24), re-run at explicit operator request; the deployed bytecode is immutable so the source is byte-identical, but all reasoning was re-derived and the on-chain context and vendored-source provenance were re-verified fresh. **The audit is clean: 0 confirmed findings.** The strongest reason: the gate is read-only and can only *revert*, the hook holds and moves no funds of its own, the fee is a compile-time constant on a non-virtual function, and all 19 vendored v4-core files are byte-identical to genuine npm. + +## 2. Scope + +- Contracts reviewed: 2/2 production (`src/TailTwins.sol`, `src/AeonFee.sol`, ~193 LOC) +- Entrypoints reviewed: 6/6 - `beforeSwap` (onlyPoolManager, view gate) and `afterSwap` (onlyPoolManager, inherited fee) are the two callbacks; `currentSqrtPriceX96`, `requiredTail`, `isAcceptable`, `acceptableAmountAtOrAbove` are pure/view helper reads +- Address audited: 0x9818dDD1102c9606Cd693aC17A7B8B17609480c4 (Base, chainid 8453) +- Not reviewed this run: the 19 vendored `@uniswap/v4-core` library/interface files under `lib/v4-core/src/` - checked for provenance (SHA-256) only, not re-audited as bespoke logic (they are unmodified upstream) + +## 3. Methodology + +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5) against the full 11-class v4-hook checklist, adversarially refuted every candidate (S6). No survivor qualified for the fuzz arm (clean audit → hard gate skip, S6.5). + +- Tools: slither(ok), agentic(ok), fuzz(skipped - clean audit) +- Provenance (MODE=onchain): 19/19 vendored `@uniswap/v4-core` files SHA-256 **IDENTICAL** to genuine npm (compared against 1.0.0 / 1.0.1 / 1.0.2, fresh tarballs this run); security-critical `FullMath.sol` and `StateLibrary.sol` both identical. 0 DIFFERENT, 0 UNVERIFIED. No supply-chain tampering. +- On-chain cross-checks (Base public RPC): native balance 0 wei; EIP-1967 impl slot all-zero (non-proxy); `poolManager()` resolves to the canonical Base v4 PoolManager; `AEON_FEE_RECIPIENT` constant == aeon treasury; address flags 0x00C4 exact-match the implemented callbacks. + +## 4. Threat model and invariants + +Actors and trust boundaries: **anyone** may swap through a pool that installed the hook and fully controls `amountSpecified` / `zeroForOne` / exact-in-vs-out / `hookData`. The **PoolManager** (0x4985…2b2b) is the sole permitted caller of both callbacks (`onlyPoolManager`) and holds the swap lock. The **fee recipient** (0xF1E9…158e) is a compile-time constant. The hook has no owner, admin, pause, or upgrade authority. The only value crossing a boundary is the 10 bps fee, moved by `poolManager.take()` straight to the treasury. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | Every state-changing callback is `onlyPoolManager`; no owner/admin/pause/upgrade surface | Unauthenticated callback = Cork-class fee/logic hijack | +| INV2 | 10 bps fee cannot be lowered, skipped, or redirected | Constants + non-virtual `afterSwap`; redirect = revenue theft | +| INV3 | Fee charged on the correct UNSPECIFIED currency in all 4 direction×exact-in/out cases; exact-output never skips it | Wrong currency/skip = silent fee bypass | +| INV4 | Fee arithmetic never overflows int128/uint256 for realistic deltas | Overflow-revert on every swap bricks the pool | +| INV5 | `beforeSwap` gate is read-only and can ONLY revert; the hook never moves currency itself | A value-moving gate could be manipulated to steal; a pure revert gate cannot | +| INV6 | Pool is never permanently bricked: a passing amount is always constructible and the swapper controls it | Permanently-reverting leg = griefing / DoS | +| INV7 | No reentrancy: only external call is `take()` to a fixed EOA under the PM lock; no mutable hook state | Cross-contract / read-only reentrancy | +| INV8 | Non-upgradeable + provenance-clean: non-proxy, `poolManager` immutable, vendored v4-core byte-identical to npm | Tampered vendored file or upgrade path = invisible backdoor | + +## 5. Findings + +No confirmed findings. + +### Candidates raised and refuted + +- **Gate unit-confusion / one-directional brick (v4-checklist, would-be HIGH → refuted).** The checklist flags gates that compare a raw amount to a token-denominated constant, or a skew/reserve gate that is a raw-price-vs-1.0 comparison in disguise (permanently reverting one leg on any non-parity pool). TailTwins is neither: it compares `|amountSpecified| & 0xff` to `sqrtPriceX96 & 0xff` under a circular tolerance - a **dimensionless modular match**, not a magnitude cap and not a reserve/skew comparison. Brute-forced: for every one of the 256 possible price tails there are exactly 33 acceptable amount low-bytes, and the swapper fully controls the low byte of their own amount, so a passing swap is always constructible in **both** directions. No permanent one-directional lock (INV6). Because it is a magnitude-agnostic last-byte test it also has **no sub-18-decimal fail-open** - it can only over-restrict (revert), never silently admit something dangerous (INV5). +- **Manipulable gate → theft (would-be CRITICAL → refuted).** The gate lives entirely in a `view` `beforeSwap` that returns `ZERO_DELTA`; its only effect is a possible revert. TailTwins never calls `poolManager.take`/`settle` or moves any currency itself - all value movement is confined to the inherited, non-virtual `AeonFee.afterSwap`. There is no state a manipulated price could steer into a transfer (INV5). +- **Fee redirect / skip (would-be HIGH → refuted).** `AEON_FEE_RECIPIENT` and `AEON_FEE_BPS` are compile-time constants and `afterSwap` is **not** `virtual`, so a derived hook cannot lower, skip, or redirect the fee. TailTwins does not override `_afterSwapExtra`, so `extra == 0`. The abs-before-guard (`if (unspecifiedAmount < 0) unspecifiedAmount = -unspecifiedAmount;`) is present, so exact-output swaps do not silently skip the fee (INV2/INV3). +- **Wrong fee currency (would-be MEDIUM → refuted).** Independently rebuilt the `(feeCurrency, unspecifiedAmount)` truth table from v4 semantics: exactIn&&zeroForOne→currency1, exactIn&&!zeroForOne→currency0, exactOut&&zeroForOne→currency0, exactOut&&!zeroForOne→currency1 - all four select the correct *unspecified* side (INV3). +- **int128.min / int256.min negation revert (would-be LOW → refuted, non-issue).** `_size(type(int256).min)` and `-unspecifiedAmount` at `type(int128).min` would revert on 0.8.26 checked arithmetic, but both require a swap magnitude ~2^255 / 2^127 - many orders beyond any real ERC20 supply at 18 decimals. Reverts a single swap, unreachable, not exploitable (INV4). +- **`acceptableAmountAtOrAbove` minimality (UX/doc gap → not a candidate).** As noted in the 2026-08-24 pass, the helper targets an exact tail match rather than the true minimal in-tolerance value, so it can suggest a larger amount than strictly required. It is a pure `view` helper with no state effect; every value it returns is safe (0 violations over 1M pairs) and the real gate re-checks independently. Documentation nit, no invariant broken. +- **`unused-return` on `getSlot0` (Slither Medium → FP).** `currentSqrtPriceX96` intentionally destructures only `sqrtPriceX96` from `getSlot0`; the other three fields are unused by design. + +## 6. Coverage and limitations + +- Explored: access control (both callbacks `onlyPoolManager`; no owner surface), reentrancy (view gate + single trusted `take` under PM lock), oracle/price manipulation (gate reads slot0 but is revert-only, moves no value), arithmetic/precision (fee rounds down, overflow-guarded), upgradeability (non-proxy, immutable poolManager, no delegatecall/selfdestruct), external-call assumptions (`take` to a fixed EOA), economic/MEV (fee fixed and mandatory; gate is a per-swap self-DoS game, no cross-user grief), and the full v4-hook checklist (flag encoding 0x00C4 exact-match, hookData ignored, flash-accounting delta = fee take only, gate unit-confusion class ruled out). Slither hit classes: 10 canonical FullMath/CustomRevert bit-hacks on SHA-verified upstream + 1 intentional partial destructure - all false positives. +- Not exercised: no fuzz campaign (clean audit, hard gate - 0 survivors to prove). The gate/escape/fee logic was instead verified by exhaustive brute force (65,536 distance pairs + 1,024,000 escape-helper pairs + the 4-case fee truth table). +- Honest partiality: a paid human audit would additionally model live pool composition / MEV interactions of the price-tail game under real order flow, and formally verify the v4 flash-accounting delta reconciliation against the PoolManager. Those are behavioral/economic concerns beyond a static source + provenance pass; none change the custody-free, revert-only risk profile. + +## 7. Appendix + +- Contract: [`0x9818dDD1102c9606Cd693aC17A7B8B17609480c4` on BaseScan](https://basescan.org/address/0x9818dDD1102c9606Cd693aC17A7B8B17609480c4) - verified source. +- Registry entry: [`hooks/tailtwins.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/tailtwins.json) - flags + every-chain addresses. +- Source: [`src/TailTwins.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/TailTwins.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/audits/totalizertrap.md b/audits/totalizertrap.md new file mode 100644 index 0000000..d3d1f52 --- /dev/null +++ b/audits/totalizertrap.md @@ -0,0 +1,67 @@ +# sc-audit report: base:0xa7a62422d13c7648ca53ad91e9268ac4bfc6c0c4 (TotalizerTrap) + +- Auditor: aeon sc-audit (autonomous agentic review + Slither + provenance) +- Date: 2026-09-07 · Mode: onchain +- Target: base:0xA7a62422d13C7648cA53ad91E9268ac4bFC6C0c4 (TotalizerTrap) +- On-chain context: non-proxy (EIP-1967 impl slot empty); native balance 0; 2,348 bytes deployed; solc 0.8.26 / cancun; hook flags 0x00C4 (BEFORE_SWAP + AFTER_SWAP + AFTER_SWAP_RETURNS_DELTA); fee recipient 0xF1E958…B158e (aeon treasury); no owner/admin, no upgrade path. +- Outcome: CLEAN (0 confirmed) +- Disclosure: none (clean); operator-gated (operator's own aeon.fun Base fleet hook). + +## 1. Executive summary +TotalizerTrap is a Uniswap v4 hook in the aeon.fun Base fleet: a novelty "trap" gate layered on the mandatory-fee `AeonFee` base. Its `beforeSwap` keeps a per-pool cumulative-size odometer and reverts any swap that would land that odometer on a multiple of 11; otherwise it advances the odometer and lets the swap through untouched. The fee half is the previously-audited, non-virtual, int128.min-hardened `AeonFee` (fixed 10 bps on the unspecified currency, routed straight to a compile-time-constant treasury). This run modeled 8 invariants and hunted a path breaking each; none broke. The single most important takeaway: the trap can never *permanently* brick a pool - at any odometer value at most 1 swap size in 11 is forbidden, and any swapper clears it by adjusting size by one unit (the contract even exposes `acceptableAmountAtOrAbove`/`trippingAmount` for exactly that). No custody, no privileged functions, vendored v4-core 14/14 SHA-identical to upstream 1.0.1. + +## 2. Scope +- Contracts reviewed: 2/2 (TotalizerTrap.sol ~71 LOC, AeonFee.sol ~102 LOC; ~173 LOC production) +- Entrypoints reviewed: 2/2 state-changing external (`beforeSwap`, `afterSwap`); 4 view helpers (`cumulativeTotal`, `wouldTrip`, `trippingAmount`, `acceptableAmountAtOrAbove`) also read. +- Address audited: 0xA7a62422d13C7648cA53ad91E9268ac4bFC6C0c4 (Base, chainid 8453) +- Not reviewed as bespoke logic this run: 14 vendored `@uniswap/v4-core` files (interfaces/types/libraries) - instead provenance-verified byte-for-byte against upstream (see §3). No dependency `lib/` deep-logic review needed since they are unmodified upstream. + +## 3. Methodology +Threat-model-first: derived 8 invariants (S5.0), hunted a path breaking each (S5), adversarially refuted every candidate (S6). Fuzz not run (S6.5 hard gate: 0 confirmed survivors). +- Tools: slither(ok), agentic(ok), fuzz(skipped - clean-audit hard gate) +- Provenance (MODE=onchain): **14/14 vendored `@uniswap/v4-core` files SHA-256 IDENTICAL to npm 1.0.1** (IHooks, IPoolManager, IProtocolFees, IExtsload, IExttload, IERC20Minimal, IERC6909Claims, CustomRevert, SafeCast, BalanceDelta, BeforeSwapDelta, Currency, PoolId, PoolKey). Zero DIFFERENT, zero UNVERIFIED - the highest-yield backdoor class (an altered line hidden inside a "just OpenZeppelin/Uniswap" file) is ruled out. The only bespoke code is TotalizerTrap.sol + AeonFee.sol. +- Fee recipient cross-check: `AEON_FEE_RECIPIENT` == 0xF1E958db7D1e4C074377946018Ad645db4FB158e, the same aeon treasury verified on every prior fleet audit. +- Prior coverage: this exact address was audited clean 2026-08-20 and 2026-08-24; today is an operator-directed re-audit via explicit `var` override. + +## 4. Threat model and invariants +Actors and trust boundaries: **anyone** may swap through the PoolManager and freely choose `amountSpecified`; the **PoolManager** (immutable, set once in the constructor) is the sole authorized caller of both hook callbacks; the **fee recipient** is a fixed compile-time constant. There is **no owner/admin and no upgrade path** - the contract exposes zero privileged functions. The one place authority/value crosses a boundary is `AeonFee.afterSwap` → `poolManager.take(feeCurrency, RECIPIENT, feeAmount)`. + +| ID | Invariant | Why it matters | +|----|-----------|----------------| +| INV1 | Only PoolManager can invoke beforeSwap/afterSwap | unauthenticated callback could move the ledger / corrupt the odometer | +| INV2 | The 10 bps fee cannot be lowered, skipped, or redirected | protocol-revenue integrity (afterSwap non-virtual; recipient+rate constants) | +| INV3 | The hook custodies no funds; every take() routes to the fixed recipient | no drainable balance | +| INV4 | Fee arithmetic never overflows / casts unsafely | a bad int128 delta reverts every swap and bricks the pool | +| INV5 | The trap can never *permanently* brick a pool (≤1-in-11 sizes forbidden; always clearable) | irreversible pool lock = fund freeze / griefing | +| INV6 | Odometer state cannot be driven into a permanently-locking value | state corruption → DoS | +| INV7 | No reentrancy path corrupts the odometer or double-takes the fee | accounting break | +| INV8 | beforeSwap returns ZERO_DELTA + 0 fee-override → never alters swap amounts / LP fee | flash-accounting delta integrity | + +## 5. Findings +No confirmed findings. + +### Candidates raised and refuted +- **[C1 · info · economic/MEV] Front-running the shared odometer to force a victim swap to revert.** The trap residue `totalOf[id] % 11` is shared mutable state, so an attacker can front-run a swap to shift it and make a victim's pending swap trip. *Refuted as a vulnerability:* this is the intended behavior of a "trap" gate, causes no fund loss, and the victim simply resubmits with a size adjusted by 1 (10 of 11 sizes always pass; `acceptableAmountAtOrAbove` computes the clearing size). The attacker pays gas + the 10 bps fee on every grief swap, and all v4 swaps are inherently front-runnable regardless. Not disclosable (INV5/INV6 hold - no permanent lock). +- **[C2 · info · arithmetic] `_size` negates `amountSpecified`; `type(int256).min` would overflow.** *Refuted:* a swap of magnitude ~2^255 is unreachable against any real pool/liquidity, and the checked-arithmetic negation reverts safely (no fund loss, no bad state) rather than misbehaving. +- **[C3 · info · design] Mixed-unit odometer.** `size` is `|amountSpecified|` in whichever currency the swap specified, so `totalOf` sums token0-denominated and token1-denominated magnitudes together. *Refuted:* a cosmetic quirk of a gimmick metric; no fund-safety invariant depends on the odometer being unit-consistent, and the gate (mod 11) is arbitrary by design. +- **[C4 · info · arithmetic] uint256 odometer overflow bricking a pool.** *Refuted:* reaching 2^256 cumulative size is impossible in any realistic timeframe; a hypothetical overflow reverts (checked math) rather than corrupting state. +- **[C5 · info · logic] Zero-size swap trips (0 % 11 == 0 at genesis).** *Refuted:* the PoolManager rejects zero-amount swaps upstream, so `size == 0` is not reachable through `beforeSwap`. +- **[C6 · medium(slither) · arithmetic] divide-before-multiply in CustomRevert.bubbleUpAndRevertWith.** *Refuted:* the flagged `mul(div(add(returndatasize(),31),32),32)` is the canonical round-up-to-word-boundary assembly idiom in vendored v4-core `CustomRevert.sol`, which is SHA-256 identical to upstream 1.0.1. False positive. + +AeonFee fee-currency selection was re-derived across all four direction × exact-in/out cases and is dimensionally correct on every case (matches upstream FeeTakingHook): the fee is charged on the unspecified currency, magnitude taken before the guard so exact-output swaps are not silently skipped, `feeAmount ≤ int128.max` require guards the cast, and `afterSwap` is non-virtual so no derived hook can override it (INV2 holds). + +## 6. Coverage and limitations +- Explored: access control (INV1 - both callbacks `onlyPoolManager`), reentrancy (INV7 - no state mutation after the external take; only-PoolManager gate + v4 lock), oracle/price (N/A - no price reads), arithmetic/precision (fee math + odometer, INV4/C2/C4), upgradeability/delegatecall (N/A - non-proxy, no delegatecall, immutable poolManager), external-call assumptions (single trusted `poolManager.take`), signatures/replay (N/A - no signatures), economic/MEV (C1 trap-griefing), and the full v4-hook checklist: permission-bit-vs-address-flag encoding (0x00C4 matches the implemented beforeSwap+afterSwap+returns-delta callbacks exactly), flash-accounting deltas (beforeSwap returns ZERO_DELTA; afterSwap returns feeDelta+extra with extra=0 for this hook and take() balancing the delta), gate unit-confusion (the gate is a pure modulus on an accumulator, NOT a token-denominated cap, so the sub-18-decimal fail-open and one-directional-price-gate classes do not apply), and DoS/economic (INV5/INV6). +- Slither: ok - 1 hit (divide-before-multiply in vendored CustomRevert.sol), a false positive on SHA-verified upstream. +- Not exercised: no fuzz campaign (hard gate - 0 confirmed survivors; the S5 reasoning is the audit and there was no invariant break to machine-prove). Vendored v4-core files were provenance-verified, not logic-re-audited (they are unmodified upstream and out of scope for bespoke review). +- Honest partiality: a paid human audit would additionally run a live fork simulation of real swaps against a deployed pool to confirm the fee `take()` settles cleanly under the PoolManager lock end-to-end, and would fuzz the odometer/fee interaction across many pools - this run relied on source reasoning plus the identical prior-fleet audits that already fork-simulated the AeonFee base. + +## 7. Appendix + +- Contract: [`0xA7a62422d13C7648cA53ad91E9268ac4bFC6C0c4` on BaseScan](https://basescan.org/address/0xA7a62422d13C7648cA53ad91E9268ac4bFC6C0c4) - verified source. +- Registry entry: [`hooks/totalizertrap.json`](https://github.com/aeonfun/univ4-hooks/blob/main/hooks/totalizertrap.json) - flags + every-chain addresses. +- Source: [`src/TotalizerTrap.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/TotalizerTrap.sol) on the shared [`src/AeonFee.sol`](https://github.com/aeonfun/univ4-hooks/blob/main/src/AeonFee.sol) 10 bps base. +- Auditor: aeon `sc-audit` - autonomous agentic source review + Slither + fuzz-on-findings, operated by @aeonframework. +- Disclosure: clean audit, nothing to disclose. aeon.fun first-party hook; onchain findings are operator-gated by policy. + +> Autonomous agent audit: threat-model-first source review, Slither, and a fuzz arm gated on findings. Thorough and case-exhaustive at the source level, but not a substitute for a paid human audit - see the Coverage and limitations section for what was and was not machine-proven. diff --git a/hooklist.json b/hooklist.json index 10ec5a6..48173c8 100644 --- a/hooklist.json +++ b/hooklist.json @@ -88,14 +88,14 @@ "note": "Dynamic swap fee, rises with volatility" }, "date": "2026-07-30", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/dynamicfee.md", "flagBits": 4292, "callbacks": [ "afterInitialize", "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "NoOp", @@ -126,13 +126,13 @@ "note": "No fee of its own" }, "date": "2026-07-31", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/noop.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "BlockEcho", @@ -163,13 +163,13 @@ "note": "No fee of its own" }, "date": "2026-08-01", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/blockecho.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "HeavierHand", @@ -203,14 +203,14 @@ "note": "No fee of its own" }, "date": "2026-08-01", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/heavierhand.md", "flagBits": 4292, "callbacks": [ "afterInitialize", "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "TailTwins", @@ -241,13 +241,13 @@ "note": "No fee of its own" }, "date": "2026-08-02", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/tailtwins.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "TotalizerTrap", @@ -278,13 +278,13 @@ "note": "No fee of its own" }, "date": "2026-08-02", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/totalizertrap.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "CapGate", @@ -318,13 +318,13 @@ "note": "No fee of its own" }, "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/capgate.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "CrownClash", @@ -355,12 +355,12 @@ "note": "0.07% of each swap to the treasury" }, "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/crownclash.md", "flagBits": 68, "callbacks": [ "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "ExactInGate", @@ -390,13 +390,13 @@ "note": "No fee of its own" }, "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/exactingate.md", "flagBits": 196, "callbacks": [ "beforeSwap", "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "LegacyLedger", @@ -427,12 +427,12 @@ "note": "0.05% of each swap to the treasury" }, "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/legacyledger.md", "flagBits": 68, "callbacks": [ "afterSwap", "afterSwapReturnDelta" - ], - "auditUrl": "" + ] }, { "name": "DailyWindowGate", @@ -461,11 +461,11 @@ }, "date": "2026-09-04", "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/dailywindowgate.md", "flagBits": 128, "callbacks": [ "beforeSwap" - ], - "auditUrl": "" + ] }, { "name": "MarketHoursGate", @@ -494,11 +494,11 @@ }, "date": "2026-09-04", "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/markethoursgate.md", "flagBits": 128, "callbacks": [ "beforeSwap" - ], - "auditUrl": "" + ] } ] } diff --git a/hooks/blockecho.json b/hooks/blockecho.json index 2fc8d5c..b5fa188 100644 --- a/hooks/blockecho.json +++ b/hooks/blockecho.json @@ -26,5 +26,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-01" + "date": "2026-08-01", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/blockecho.md" } diff --git a/hooks/capgate.json b/hooks/capgate.json index 54b9b59..d5583e6 100644 --- a/hooks/capgate.json +++ b/hooks/capgate.json @@ -29,5 +29,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-03" + "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/capgate.md" } diff --git a/hooks/crownclash.json b/hooks/crownclash.json index 8390262..911b49f 100644 --- a/hooks/crownclash.json +++ b/hooks/crownclash.json @@ -26,5 +26,6 @@ "recipient": "treasury", "note": "0.07% of each swap to the treasury" }, - "date": "2026-08-03" + "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/crownclash.md" } diff --git a/hooks/dailywindowgate.json b/hooks/dailywindowgate.json index 6febe68..3b609c2 100644 --- a/hooks/dailywindowgate.json +++ b/hooks/dailywindowgate.json @@ -24,5 +24,6 @@ "note": "No fee of its own" }, "date": "2026-09-04", - "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8" + "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/dailywindowgate.md" } diff --git a/hooks/dynamicfee.json b/hooks/dynamicfee.json index efc9f9a..b237c9d 100644 --- a/hooks/dynamicfee.json +++ b/hooks/dynamicfee.json @@ -26,5 +26,6 @@ "recipient": "LP", "note": "Dynamic swap fee, rises with volatility" }, - "date": "2026-07-30" + "date": "2026-07-30", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/dynamicfee.md" } diff --git a/hooks/exactingate.json b/hooks/exactingate.json index d067cd6..b533654 100644 --- a/hooks/exactingate.json +++ b/hooks/exactingate.json @@ -25,5 +25,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-03" + "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/exactingate.md" } diff --git a/hooks/heavierhand.json b/hooks/heavierhand.json index a794644..9406b75 100644 --- a/hooks/heavierhand.json +++ b/hooks/heavierhand.json @@ -29,5 +29,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-01" + "date": "2026-08-01", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/heavierhand.md" } diff --git a/hooks/legacyledger.json b/hooks/legacyledger.json index a5238b0..ef22246 100644 --- a/hooks/legacyledger.json +++ b/hooks/legacyledger.json @@ -26,5 +26,6 @@ "recipient": "treasury", "note": "0.05% of each swap to the treasury" }, - "date": "2026-08-03" + "date": "2026-08-03", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/legacyledger.md" } diff --git a/hooks/markethoursgate.json b/hooks/markethoursgate.json index 855a892..103dd0e 100644 --- a/hooks/markethoursgate.json +++ b/hooks/markethoursgate.json @@ -24,5 +24,6 @@ "note": "No fee of its own" }, "date": "2026-09-04", - "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8" + "deployer": "0x0DE98e661606EcFC448da7A8276DdE4de45200C8", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/markethoursgate.md" } diff --git a/hooks/noop.json b/hooks/noop.json index fedd584..604ddb1 100644 --- a/hooks/noop.json +++ b/hooks/noop.json @@ -26,5 +26,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-07-31" + "date": "2026-07-31", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/noop.md" } diff --git a/hooks/tailtwins.json b/hooks/tailtwins.json index 8030b3b..db43c03 100644 --- a/hooks/tailtwins.json +++ b/hooks/tailtwins.json @@ -26,5 +26,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-02" + "date": "2026-08-02", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/tailtwins.md" } diff --git a/hooks/totalizertrap.json b/hooks/totalizertrap.json index bb08625..a9c784d 100644 --- a/hooks/totalizertrap.json +++ b/hooks/totalizertrap.json @@ -26,5 +26,6 @@ "recipient": "", "note": "No fee of its own" }, - "date": "2026-08-02" + "date": "2026-08-02", + "auditUrl": "https://github.com/aeonfun/univ4-hooks/blob/main/audits/totalizertrap.md" }