Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions audits/README.md
Original file line number Diff line number Diff line change
@@ -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).
63 changes: 63 additions & 0 deletions audits/blockecho.md
Original file line number Diff line number Diff line change
@@ -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.
52 changes: 52 additions & 0 deletions audits/capgate.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading