Repo: syed-ghufran-hassan/orderbook · 1 contract · ~85 lines
Audited: February 21, 2026
A minimal DeFi orderbook on Stacks allowing users to deposit balances, place orders, and fill orders with partial-fill support. The contract demonstrates basic Clarity map operations but implements a simulated orderbook with no real token transfers — deposits create balance from nothing.
| Severity | Count | Description |
|---|---|---|
| CRITICAL | 2 | Phantom deposits, no withdrawal |
| HIGH | 2 | Single-dimension trade, no cancel |
| MEDIUM | 3 | Zero amounts, no price, fill edge case |
| LOW | 2 | No events, no cleanup |
The deposit function increments an internal balance map without any stx-transfer? or token transfer. Users can call deposit with any amount to create unlimited fake balance.
(define-public (deposit (amount uint))
(begin
(map-set balances {user: tx-sender}
(+ (default-to u0 (map-get? balances {user: tx-sender})) amount))
(ok true)))
The entire economic model is meaningless — anyone can mint unlimited balance with zero cost.
Fix: Add (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) before crediting the balance.
There is no withdraw function. If the contract ever held real STX (after fixing C-01), deposited funds could never be recovered. Users have no exit path.
Fix: Add a withdraw function that debits the balance map and transfers STX back to the user.
When a taker fills an order, the contract transfers the taker's balance to the maker. But there's only one token dimension — there is no "quote" asset. Filling an order just moves the same token back to the maker, making trades economically circular (taker pays X of token A, maker receives X of token A).
A real orderbook needs two assets: base-token and quote-token, with orders specifying a price (ratio between them).
Once an order is placed via add-order, the maker's balance is locked in the order with no way to cancel and reclaim it. The only recovery is having someone else fill the order — which just returns the same token type (see H-01).
Fix: Add cancel-order that verifies (is-eq tx-sender (get trader order)), returns locked amount to maker's balance, and sets active: false.
Neither add-order nor fill-order validates that amounts are greater than zero. Users can create zero-amount orders (wasting map storage) and zero-amount fills (no-op transactions that still modify state).
Fix: Add (asserts! (> amount u0) err-invalid-amount) to both functions.
Orders contain only {trader, amount, active} — no price field. Without price, there's no bid/ask spread, no matching engine, and no price discovery. This is effectively a FIFO queue, not an orderbook.
A functional orderbook needs at minimum: price: uint, side: (string-ascii 4) ("buy"/"sell"), and a matching algorithm.
Since zero fills are allowed (M-01), the fill-order active-status check works correctly but allows no-op fills that still trigger state writes:
(let ((new-amount (- (get amount order) fill-amount)) ;; unchanged
(new-active (if (>= fill-amount (get amount order)) ;; still true
false true)))
(map-set orders ...) ;; unnecessary write
No print statements for deposits, order creation, fills, or any state changes. Off-chain indexers and UIs cannot track contract activity without polling every block.
Filled orders remain in the map permanently with active: false. No mechanism exists to clean up stale entries or set expiry times on orders. Over time this wastes on-chain storage.
The contract demonstrates basic Clarity patterns (maps, variables, assertions, partial fills) but falls far short of a functional DEX:
| Feature | Status |
|---|---|
| Real token transfers (STX or SIP-010) | ❌ Missing — phantom balances |
| Two-sided market (base/quote) | ❌ Single dimension |
| Price / matching engine | ❌ No price field |
| Order cancellation | ❌ No cancel function |
| Withdrawal | ❌ No withdraw function |
| Balance tracking | ✅ Internal map |
| Order creation | ✅ With counter |
| Partial fills | ✅ Correct accounting |
| Read-only queries | ✅ get-order, get-balance |
Verdict: Proof-of-concept / learning exercise demonstrating Clarity fundamentals. The partial-fill logic is correctly implemented, but the contract cannot serve as a real DEX without token transfers, price discovery, and a two-asset model. Not suitable for any value transfer.