# CLOB Audit — Durianfun V2.5

## 🛡️ CLOB Audit — Durianfun V2.5

> **6 rounds. 3 criticals found. 3 criticals patched. All tests green. Mainnet-ready.**

### Scope

Contracts reviewed across six rounds (R1–R6):

| Contract                       | Purpose                                                      |
| ------------------------------ | ------------------------------------------------------------ |
| `CLOBFactory.sol`              | Listing governance, fee control, pair deployment via CREATE2 |
| `CLOBPair.sol`                 | Per-pair orderbook + matching engine                         |
| `DurianSpotWallet.sol` (Vault) | Singleton balance ledger, lock/unlock, fee routing           |
| `InsuranceReserve.sol`         | Protocol insurance fund, 10% of net fees                     |
| `AmmFeeRouterV2.sol`           | Peripheral (non-critical path)                               |

**Methodology:** Manual code review · Static analysis (Slither) · Fuzz testing (Echidna-style property tests) · Invariant testing · Explicit attack simulation · Bytecode verification on Kubscan.

**Compiler:** Solidity ^0.8.24 · EVM target Paris **Current version:** V2.5 (post-C-02 patch redeploy from V2.0) **Tests:** 29 / 29 passing

### Review Rounds

| Round  | Focus                                        | Outcome                                             |
| ------ | -------------------------------------------- | --------------------------------------------------- |
| **R1** | Initial logic review on V1.5 testnet         | Feature surface finalized; V1.5 deprecated for V2.0 |
| **R2** | Vault ↔ Pair separation, native refund paths | 4 findings (P2 series), all patched in V2.0         |
| **R3** | Fuzz on matching engine                      | No critical findings; added `MIN_TRADE_NOTIONAL`    |
| **R4** | Gas-DoS review (tick-hop traversal)          | 1 medium (M-01), 1 low (L-01), both patched         |
| **R5** | Invariant review (locked-balance accounting) | Added per-order `baseLocked` storage slot           |
| **R6** | Hard pre-mainnet audit                       | 3 findings (C-01, H-01, M-02) — all patched         |

### Findings Summary

|        Severity        |  Count  | Status                         |
| :--------------------: | :-----: | ------------------------------ |
|       🔴 Critical      |    3    | ✅ All fixed (C-01, C-02, C-03) |
|         🟠 High        |    1    | ✅ Fixed (H-01)                 |
|        🟡 Medium       |    2    | ✅ All fixed (M-01, M-02)       |
|         🔵 Low         |    1    | ✅ Fixed (L-01)                 |
| ⚪ Production hardening | 8 items | ✅ All shipped                  |

### Critical Findings — All Resolved

#### C-01 · `initialize()` Front-Run Window (R6)

**File:** `CLOBPair.sol:170`

**Issue:** The pair's `initialize(token, base, tickSize, ...)` function could be called by any address between clone creation and factory initialization. An attacker monitoring the mempool could front-run the factory's init call and set malicious fee configuration or ownership.

**Impact:** Hijack every newly-listed pair with malicious parameters.

**Fix (defense-in-depth, two layers):**

1. **Access control:** `require(msg.sender == _factory, "not factory");`
2. **Atomic deployment:** Factory now performs `Clones.cloneDeterministic(...)` and `pair.initialize(...)` in the **same transaction**, eliminating the window entirely.

**Status:** ✅ Fixed. Bytecode-verified on Kubscan.

***

#### C-02 · Maker Fee > Effective Rebate DOS

**File:** `CLOBFactory.sol:199`

**Issue:** `setFees(takerPpm, makerPpm, rebateBps)` allowed any legal individual-bound config — including values where `makerPpm > (takerPpm × rebateBps / 10,000)`. When that pair then processed a trade, settlement tried to pay the maker more than the protocol had pre-locked from the taker, reverting every time.

**Impact:** Total DOS on any pair configured with pathological fees. No trades execute.

**Fix:** Single-line invariant guard in `setFees`:

```solidity
require(
  uint256(_makerPpm) * 10_000 <= uint256(_takerPpm) * uint256(_rebateBps),
  "maker exceeds effective rebate"
);
```

**Status:** ✅ Fixed. **V2.5 is a distinct deployment from V2.0 specifically because of this patch.** (Old V2.0 pairs cannot be retroactively patched — immutable.)

***

#### C-03 · `nextOrderId = 0` Sentinel Collision

**File:** `CLOBPair.sol`, `initialize(...)`

**Issue:** Clone `initialize()` did not explicitly set `nextOrderId`, leaving it at the Solidity default `0`. The linked-list implementation uses `0` as the "end of list" sentinel — so the **first order** placed on any new pair would receive `orderId = 0`, colliding with the sentinel and breaking traversal.

**Impact:** First order on every newly-listed pair would be untraversable by `getUserOpenOrders`, `cancelOrder` iteration, and book-depth views.

**Fix:**

```solidity
nextOrderId = 1;   // 0 is reserved sentinel
```

**Status:** ✅ Fixed. Bytecode-verified on Kubscan.

### High Finding — Resolved

#### H-01 · `transferInternal()` NatSpec Regulatory Framing (R6)

**File:** `DurianSpotWallet.sol:145`

**Issue:** The pre-R6 NatSpec comment on `transferInternal()` described the function as *"enabling OFAC-freeze evasion for sanctioned counterparties"*. The **function behavior** was a standard internal balance transfer — entirely innocuous. The **comment** created reputational and regulatory risk.

**Impact:** Zero on-chain — pure disclosure/regulatory optics.

**Fix:** Comment rewritten to neutral operational description. Zero behavior delta, zero bytecode change to the function itself (NatSpec doesn't compile into bytecode).

**Status:** ✅ Fixed.

### Medium Findings — Resolved

#### M-01 · Tick-Hop Gas-Bomb (R5)

**File:** `CLOBPair.sol`, `_insertTickIntoSortedList(...)`

**Issue:** Inserting a new price tick required linear traversal of the sorted tick list. An attacker could populate the book with hundreds of dust-level orders across distinct ticks, inflating traversal cost for every subsequent insertion.

**Impact:** Legitimate makers' orders griefed with 10×+ gas cost, potentially exceeding block gas limit.

**Fix:**

```solidity
uint256 constant MAX_TICK_HOPS = 64;
```

Enforced in traversal loop — attempts to insert past the cap revert with a clear error, but normal book depth (dozens of active ticks per side) is unaffected.

**Status:** ✅ Fixed in V2.0.

***

#### M-02 · `quoteMarketBuy()` Silent-Lie Sentinel (R6)

**File:** `CLOBPair.sol:1040`

**Issue:** The V1.5-era `quoteMarketBuy(baseAmountIn)` function returned `(0, 0, 0, true)` for certain invalid inputs. UIs rendering the quote saw `"0 cost, 0 tokens, valid"` — silent lie. Users then submitted the trade and hit a revert on-chain.

**Impact:** Poor UX, wasted gas, potential for automated-trader losses.

**Fix:** Replaced the garbage return with an explicit revert guiding integrators to the V2.0 API:

```solidity
revert("V2.0: use quoteMarketBuyByToken(tokenAmountIn)");
```

**Status:** ✅ Fixed in V2.0.

### Low Finding — Resolved

#### L-01 · Buy-Maker Rounding Residue Stranded (R5)

**File:** `CLOBPair.sol`, `Order` struct

**Issue:** Partial fills at small sizes could leave 1–2 wei of base token stranded on the pair contract — never claimable, never refundable. Violated the invariant:

```
pair.vaultLocked(base) == Σ (orders[].baseRemaining × price)
```

**Impact:** Trust/auditability of accounting invariant; economic impact negligible (wei-scale).

**Fix:** Added a per-order `uint128 baseLocked` storage slot tracking the exact base amount locked per order. On partial fills, the pair drains exactly `fillAmount × price / divisor` and resets the remainder. On cancellation or terminal fill, the residue is unlocked atomically.

**Status:** ✅ Fixed in V2.0. Invariant verified in `tests/invariants/baseLocked.spec.ts`.

### Production Hardening Checklist (V2.0 → V2.5)

All 8 items **shipped** pre-mainnet:

| # | Check                                                                          | Status |
| - | ------------------------------------------------------------------------------ | :----: |
| 1 | `msg.value == 0` enforced on all ERC-20 paths (prevents native-in-ERC20 traps) |    ✅   |
| 2 | Tick-list traversal bounded (`MAX_TICK_HOPS = 64`)                             |    ✅   |
| 3 | Per-order `baseLocked` tracking (closes rounding-residue bug)                  |    ✅   |
| 4 | O(1) user-order removal (swap-pop + index) — safe cancellation at scale        |    ✅   |
| 5 | Market-order API split (`maxBaseIn` / `minBaseOut`) — real slippage protection |    ✅   |
| 6 | `MIN_TRADE_NOTIONAL` guard — kills dust-fee dodging                            |    ✅   |
| 7 | Token decimals upper-bound (≤30) — guards against broken token math            |    ✅   |
| 8 | `OrderFilled` event reliability for indexers                                   |    ✅   |

### Attack Simulations

| # | Attack Vector                            |                                    Result                                   |
| - | ---------------------------------------- | :-------------------------------------------------------------------------: |
| 1 | Front-run on clone initialisation (C-01) |               ✅ Blocked by access control + atomic deployment               |
| 2 | Fee-config DOS (C-02)                    |                         ✅ Blocked by invariant check                        |
| 3 | Tick-hop gas-bomb (M-01)                 |                      ✅ Blocked by `MAX_TICK_HOPS = 64`                      |
| 4 | Market-order gas exhaustion              |                        ✅ Capped at 50 fills per sweep                       |
| 5 | Vault impersonation by forked pair       |                      ✅ Rejected by CREATE2 verification                     |
| 6 | Insurance-reserve drain (post-launch)    | ✅ Insurance disabled at launch; treasury gets 100% until explicitly enabled |
| 7 | Reentrancy on settlement                 |           ✅ Guarded by check-effects-interactions + per-slot mutex          |

### Bytecode Verification

| Contract            | Kubscan                                                                                                                            |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Factory V2.5        | [0x1e963da022030D29D952e7e0c944F6bfAC50b0e7#code](https://www.kubscan.com/address/0x1e963da022030D29D952e7e0c944F6bfAC50b0e7#code) |
| Vault (Spot Wallet) | [0xF5b0137F6dCEcE06C566FCae10694dD8645283B3#code](https://www.kubscan.com/address/0xF5b0137F6dCEcE06C566FCae10694dD8645283B3#code) |
| Pair Implementation | [0x6619496380EBD9FF50805E2e2637b48943875e00#code](https://www.kubscan.com/address/0x6619496380EBD9FF50805E2e2637b48943875e00#code) |

All source-verified with exact pragma, optimizer settings, and byte-identical deployed bytecode match.

### Known Off-Chain Guardrails

Not contract bugs — responsibilities of the frontend and operators:

| Guardrail                                  | Responsibility                                                   |
| ------------------------------------------ | ---------------------------------------------------------------- |
| Rebasing / fee-on-transfer token blacklist | Frontend listing form must reject                                |
| Vault address prediction match             | Deploy script must verify CREATE2 salt before factory invocation |

Both are documented in the deployment runbook (`MAINNET_DEPLOY_PLAN.md`).

### External Audits

**None to date.** All six review rounds conducted internally by the Durian engineering team using manual review + fuzz + invariant testing + explicit attack simulation.

The contracts were explicitly designed to make third-party auditing easy:

* **Immutable** — no upgrade surface to re-audit.
* **Source-verified** — bytecode matches GitHub.
* **Deterministic** — CREATE2 salts reproducible.
* **No proxies** — linear compile → deploy → done.

External audit engagements are under evaluation for future major releases (V3+).

### Responsible Disclosure

* **Email:** <durianandfun@gmail.com> (subject prefix `[SECURITY]`)
* **SLA:** 72-hour acknowledgement
* **Timeline:** 30-day coordinated disclosure
* **Bounty:** Reasonable bounties for high-impact V2.5 findings (team discretion)

→ Back to: **How the CLOB Works** · **How to List Your Coin** → Or read the **full cross-product Audit Report**.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://durianandfun.gitbook.io/durianfun/durian-spot/spot-clob/clob-audit-durianfun-v2.5.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
