Introduction
We express our gratitude to the Somnia team for the collaborative engagement that enabled the execution of this Smart Contract Security Assessment.
The Somnia Spot DEX is a central limit order book (CLOB) spot trading protocol deployed on the Somnia blockchain. It enables users to place, match, and settle limit and market orders for ERC-20 token pairs, with conditional stop-loss and take-profit orders triggered automatically via Somnia's native on-chain reactivity precompile.
Document | |
|---|---|
| Name | Smart Contract Code Review and Security Analysis Report for Somnia |
| Audited By | Ivan Bondar; Seher Saylık |
| Approved By | Kerem Solmaz |
| Website | https://somnia.network/→ |
| Changelog | 01/05/2026 - Preliminary Report |
| 20/05/2026 - Final Report | |
| Platform | Somnia |
| Language | Solidity |
| Tags | Order Book DEX; Vault; Upgradable; Fungible Token; Proxy |
| Methodology | https://docs.hacken.io/methodologies/smart-contracts→ |
Document
- Name
- Smart Contract Code Review and Security Analysis Report for Somnia
- Audited By
- Ivan Bondar; Seher Saylık
- Approved By
- Kerem Solmaz
- Website
- https://somnia.network/→
- Changelog
- 01/05/2026 - Preliminary Report
- 20/05/2026 - Final Report
- Platform
- Somnia
- Language
- Solidity
- Tags
- Order Book DEX; Vault; Upgradable; Fungible Token; Proxy
Review Scope | |
|---|---|
| Repository | https://github.com/somnia-chain/dream-dex-spot-audit→ |
| Commit | 8667425 |
| Final Commit | 9015ac4 |
Review Scope
- Commit
- 8667425
- Final Commit
- 9015ac4
Audit Summary
The system users should acknowledge all the risks summed up in the risks section of the report
{Finding_Table?columns=title,severity,status&setting.filter.type=Vulnerability}
Documentation quality
Functional requirements are detailed.
Project overview is detailed
All roles in the system are described.
Use cases are described and detailed.
For each contract, all futures are described.
All interactions are described.
Technical description is detailed.
Run instructions are provided.
Technical specification is provided.
The NatSpec documentation is sufficient.
Code quality
The development environment is configured.
Test coverage
Code coverage of the project is 81.94 (branch coverage).
Deployment and core user interactions are thoroughly covered with high line and statement coverage.
Mixed-decimal and upgrade scenarios are well tested.
Negative-path and edge-case branch coverage has gaps in several contracts.
Reentrancy and concurrent multi-user interaction scenarios lack dedicated tests.
System Overview
The protocol is composed of two deployable contracts, SpotPool and SpotStopOrderRegistry, both designed for deployment behind upgradeable proxies using ERC-7201 namespaced storage. The out-of-scope deployer libraries support both UpgradeableBeacon + BeaconProxy and TransparentUpgradeableProxy patterns. SpotPool inherits from the abstract OrderBook matching engine and the abstract ERC20Vault, forming a self-contained spot market for a single base/quote token pair. ERC-20 tokens (or the chain's native token via a sentinel address) are deposited into the vault, which tracks internal balances. When an order is placed, the required tokens (plus maker fees for the on-book portion, rounded up via ceiling division) are locked from the user's internal balance. The matching engine performs a dry-run/real-run two-pass fill against the opposite side of the book, transfers tokens between maker and taker internal balances on each fill, and charges fees (floor-rounded) to a configurable fee recipient. Fees use a sub-basis-point precision unit (BPS_TIMES_1K, where 1 BPS = 1,000 units). Supported order types include NormalOrder, FillOrKill, ImmediateOrCancel, and PostOnly, with self-matching prevention via CancelTaker or CancelMaker options.
Price-time priority within the order book is maintained by a composite data structure: a Red-Black Tree of unique price-priority levels combined with an intrusive doubly-linked list for FIFO ordering within each level and efficient full-book traversal. Order indices are allocated via a LIFO free-list (OrderIndexAllocator) to minimize new storage slot creation, and each OrderId encodes both a reusable slot index and a monotonically increasing unique counter to prevent stale-reference confusion. Per-user open order tracking is handled through a separate linked-list index (PerUserOrderIndex), and expired orders are lazily cleaned up during order placement and book traversal.
SpotStopOrderRegistry is a standalone upgradeable contract that holds pending stop orders with GTE (greater-than-or-equal) or LTE (less-than-or-equal) trigger conditions on the midpoint price. It subscribes to the SpotPool's MarkPriceUpdated event via the Somnia reactivity precompile at 0x0100. When the precompile invokes the registry's onEvent callback, all orders whose trigger conditions are satisfied are processed: each is submitted as an IOC order to the SpotPool via placeOrderFor, with failures captured by try-catch to avoid blocking sibling orders. Users pay SOMI (the chain's native token) per order creation to fund handler invocation gas; SOMI is refunded on cancellation and consumed on trigger. An admin (via OwnableUpgradeable) manages subscription lifecycle, fee parameters, slippage tolerance, and excess SOMI withdrawals.
Files in Scope
Common.sol: Defines the
OrderIndexandOrderIduser-defined value types, their packing/unpacking helpers, theNATIVE_TOKENsentinel address used by the vault for native token accounting, timestamp conversion to nanoseconds, ceiling/floor division utilities, absolute-value helpers, BPS-based fee calculation functions, and theOrderIndexAllocatorlibrary that manages a LIFO free-list for reusable order slot indices.IERC20Vault.sol: Declares the interface for the internal-balance vault, specifying
deposit,depositNative,withdraw, andgetWithdrawableBalance, along with associated error types for insufficient balance, zero amounts, and native token transfer failures.IOrderBook.sol: Declares the interface for the central limit order book, defining the
Order,OrderBookParameters, andOrderBookLevelstructs, theOrderTypeandSelfMatchingOptionenums, and the external functions forplaceOrder,placeOrderFor,cancelOrder,reduceOrder,getBookLevels, and paginated off-chain order iteration.ISpotPool.sol: Extends
IOrderBookandIERC20Vaultto define the spot pool interface, addingupdateFeeRecipient,placeTakerOrderWithoutVault(a combined deposit-trade-withdraw convenience function),convertToQuoteAtPriceCeil, andgetPoolParamsfor reading all pool configuration in a single call.ISpotStopOrderRegistry.sol: Declares the interface for the stop order registry, defining the
PendingOrderType,Operatorenums, thePendingOrderWithTriggerandStoredPendingOrderstructs, and external functions forcreatePendingOrder,cancelPendingOrder,claimSomi, subscription management, and admin configuration setters.ISomniaReactivity.sol: Declares the minimal interface for the Somnia reactivity precompile at
0x0100, specifying theSubscriptionDatastruct and thesubscribe/unsubscribefunctions used by the stop order registry to create and remove event subscriptions.LinkedList.sol: Implements an intrusive doubly-linked list library keyed by
uint64values, providinginsertNodeBefore,insertNodeAfter,eraseNode,getNextKey, andgetPreviousKeyoperations. Multiple independent logical lists are supported within a single storage mapping, using zero as the empty sentinel.OrderBook.sol: Abstract contract implementing the core CLOB matching engine. It manages order storage, the dry-run/real-run two-pass matching loop against the opposite book side, expired-order cleanup, per-user order tracking, and order lifecycle hooks (
_onOrderPlaced,_onOrderAddedToBook,_onOrderFilled,_onOrderRemoved,_onOrderReduced) that concrete subclasses override to implement token locking and settlement.OrderIndexManager.sol: Abstract contract that wraps
OrderIndexAllocatorwith a monotonically increasing unique counter to produce globally uniqueOrderIdvalues, providing_getNextOrderIdand_freeOrderIndexfor subclasses.PerUserOrderIndex.sol: Library that maintains a per-address linked list of open order indices, supporting O(1)
addOrder(prepend),removeOrder,getLatestOrderForUser, andgetNextUserOrdertraversal.PriorityIndex.sol: Library that combines a Red-Black Tree of unique price-priority levels with a doubly-linked list of order indices to maintain price-time priority ordering. It provides
insertOrder,eraseOrder,getBestOrder, andgetNextBestOrder, with price-to-priority conversion that inverts bid prices so higher bids map to lower (better) priority keys.SpotStopOrderRegistry.sol: Concrete upgradeable contract that stores pending stop orders with GTE/LTE trigger conditions, manages a Somnia reactivity subscription to
MarkPriceUpdatedevents, processes triggered orders as IOC submissions to the associated SpotPool viaplaceOrderFor, handles SOMI payment collection and refund (with an unclaimed fallback for contracts that cannot receive native tokens), and exposes admin functions for subscription lifecycle and configuration.SpotPool.sol: Concrete upgradeable contract that combines the OrderBook matching engine with the ERC20Vault to form a complete spot trading market. It implements all order lifecycle hooks to lock/unlock base and quote tokens, charges maker and taker fees on fills, computes and emits the midpoint mark price, validates order book parameters against zero-quote-fill dust attacks, and provides
placeTakerOrderWithoutVaultfor atomic deposit-trade-withdraw execution.ERC20Vault.sol: Abstract contract providing internal-balance accounting for ERC-20 tokens and the chain's native token (via a sentinel address). It exposes
deposit,depositNative, andwithdrawwithnonReentrantguards, and internal helpers_depositFor,_withdrawFor,_transfer, and_useBalanceused by SpotPool for token locking and settlement.RedBlackTree.sol: Vendored implementation of BokkyPooBah's Red-Black Tree library, providing O(log n)
insert,remove,first,last,next, andprevoperations on a self-balancing binary search tree ofuint256keys. It is used by PriorityIndex to maintain sorted unique price-priority levels.
Privileged roles
SpotStopOrderRegistry.sol
owner (inherited from OwnableUpgradeable): Administrative control over the registry's configuration, subscriptions, and excess SOMI funds.
Can call
createSubscriptionto create a Somnia reactivity subscription for automated order triggering.Can call
removeSubscriptionto remove the active reactivity subscription, rendering all existing pending orders inert.Can call
setSomiPaymentPerOrderto update the SOMI payment required per pending order creation.Can call
setSlippageToleranceBpsto update the slippage tolerance applied to triggered market orders.Can call
withdrawSomito withdraw excess SOMI (not reserved for pending-order refunds or unclaimed balances) to an arbitrary recipient.Can call
transferOwnershipto transfer the owner role to a new address.Can call
renounceOwnershipto irrevocably renounce the owner role.
Somnia Reactivity Precompile (
address(0x0100)): Sole caller authorized to deliver on-chain event callbacks.Can call
onEventto trigger pending stop orders when the SpotPool emits aMarkPriceUpdatedevent, causing matched orders to be placed into the spot order book.
SpotPool.sol
owner (inherited from OrderBook, which inherits OwnableUpgradeable): Administrative control over order book parameters and the approved-contracts allow-list.
Can call
updateOrderBookParametersto update tick size, minimum quantity, and lot size.Can call
updateIsApprovedContractToPlaceOrdersto approve or revoke contracts authorized to place orders on behalf of users.Can call
transferOwnershipto transfer the owner role to a new address.Can call
renounceOwnershipto irrevocably renounce the owner role.
Approved contracts (managed by owner via
updateIsApprovedContractToPlaceOrders): Contracts on the allow-list that may act on behalf of arbitrary users.Can call
placeOrderForto place orders in the order book on behalf of any specified owner address.
feeRecipient (set at initialization, self-managed): The address that accumulates trading fees and controls its own succession.
Can call
updateFeeRecipientto nominate a new fee recipient address (cannot be the zero address or the current recipient).
Potential Risks
External Precompile and Infrastructure Dependency: SpotStopOrderRegistry depends on Somnia's native reactivity precompile at hardcoded address 0x0100 for subscription creation (subscribe), removal (unsubscribe), and event-driven callback delivery to onEvent. The precompile's availability, callback delivery guarantees, and gas-deduction behavior are controlled entirely by the Somnia validator set and are outside the protocol's control. If the precompile ceases to deliver callbacks (due to validator changes, precompile upgrades, or insufficient SOMI balance on the subscription owner), all pending stop orders become inert until the subscription is re-established.
Incompatible Token Support Without Enforcement: The protocol does not support rebasing, fee-on-transfer, or hook-bearing (ERC-777) tokens, yet there is no on-chain enforcement to prevent their use. While currently mitigated through permissioned deployment, if pool creation is opened to third parties, unsupported tokens could be introduced, leading to incorrect accounting, unexpected behavior, or loss of funds.
Off-Chain Keeper Dependency for Expired-Order Cleanup: Expired entries are skipped via getNextBestOrder and remain in the priority index until evicted by the owner's next placement (_cleanupUserExpiredOrders) or by a permissionless call to the cancelExpiredOrders(OrderId[]) / sweepExpiredAtLevel(bool, uint256, uint256) entry points. Without continuous off-chain keepers invoking these sweepers, expired orders accumulate at busy price levels, increasing per-match cost linearly with the number of stale entries and degrading both _matchOrderToBook and _checkAndEmitMidpointChange performance. Deployment of dedicated keeper infrastructure is an operational prerequisite for production health and is outside the protocol's on-chain control surface.
Approved Contract Authority Over User Vault Balances: The placeOrderFor function in OrderBook allows any address present in the isApprovedContractToPlaceOrders mapping to place orders on behalf of arbitrary users, directly consuming those users' deposited vault balances without per-order user consent. The owner populates this mapping via updateIsApprovedContractToPlaceOrders. If a compromised or malicious contract is approved, it can place unfavorable orders that drain any user's internal vault balance.
Unrestricted State Modification by Owner: The owner of SpotPool can modify order book parameters (tickSize, minQuantity, and lotSize) at any time via updateOrderBookParameters with no upper-bound constraints beyond the zero-quote-fill check and minQuantity != 0. The owner of SpotStopOrderRegistry can change somiPaymentPerOrder and slippageToleranceBps via setSomiPaymentPerOrder and setSlippageToleranceBps respectively, with immediate effect. Parameter changes take effect on all subsequent orders and may invalidate assumptions under which existing resting or pending orders were placed.
Absence of Timelock Mechanisms for Critical Operations: All administrative functions across SpotPool and SpotStopOrderRegistry (including updateOrderBookParameters, updateIsApprovedContractToPlaceOrders, setSomiPaymentPerOrder, setSlippageToleranceBps, createSubscription, removeSubscription, and withdrawSomi) execute immediately upon the owner's call without any enforced delay or review period. There is no on-chain buffer for users or external monitors to observe and react to potentially harmful parameter changes before they take effect.
Treasury Withdrawal Authority: The withdrawSomi function in SpotStopOrderRegistry allows the owner to withdraw SOMI from the contract up to the amount exceeding reservedSomi + totalUnclaimedSomi. While user refund balances are arithmetically protected, the withdrawn excess originates from SOMI consumed by triggered stop orders and is intended to fund the reactivity subscription's gas costs. Owner withdrawal of this operational surplus could deplete the contract's SOMI balance below the 32 SOMI minimum required to maintain the active subscription.
Single Points of Failure and Control: The owner role across both SpotPool and SpotStopOrderRegistry concentrates trading parameter configuration, contract approval, subscription management, SOMI withdrawal, and (indirectly through the proxy admin) implementation upgrades under a single address. Both contracts derive access control from OwnableUpgradeable, gating all privileged operations to this single owner with no on-chain enforcement of multi-signature approval or separation of duties. If the owner is an externally owned account, compromise of a single private key would grant full control over trading parameters, approved contracts, fund withdrawals, and implementation upgrades.
Flexibility and Risk in Proxy Upgrades: Both SpotPool and SpotStopOrderRegistry are designed for deployment behind OpenZeppelin v5.5.0 upgradeable proxies with ERC-7201 namespaced storage across multiple distinct storage namespaces (somnia.storage.OrderBook, somnia.storage.OrderIndexManager, somnia.storage.SpotPool, somnia.storage.ERC20Vault for SpotPool; somnia.storage.OrderIndexManager and somnia.storage.SpotStopOrderRegistry for the registry). While namespaced storage reduces collision risk, future upgrades must maintain layout compatibility across all namespaces simultaneously, and an incorrectly structured upgrade can corrupt any of these storage regions.
Absence of Upgrade Window Constraints: The proxy admin can invoke an implementation upgrade at any time without a mandatory waiting period, cooling-off window, or governance approval step. A malicious or erroneous upgrade takes effect immediately for all affected proxies upon the single transaction's confirmation, leaving no opportunity for users to withdraw funds or cancel orders in response to announced changes.
Findings
Code ― | Title | Status | Severity | |
|---|---|---|---|---|
| F-2026-1647 | Unbounded Loop in onEvent Combined With No-Retry Precompile Semantics Enables Griefing of Stop-Order Execution | fixed | High | |
| F-2026-1618 | Unchecked Addition in _checkAndEmitMidpointChange Leads to Permanent Denial of Service on Bid Operations | fixed | High | |
| F-2026-1659 | Unsmoothed SpotPool Midpoint Enables Midpoint-Manipulation Sandwich Attacks | fixed | Medium | |
| F-2026-1656 | Reactivity Precompile Drains Contract Balance Without On‑Chain Accounting | mitigated | Medium | |
| F-2026-1616 | Tiny Partial Fills Can Truncate Quote To Zero — Taker Receives Base Without Paying | fixed | Medium | |
| F-2026-1662 | Market Pending Orders Accept Arbitrary limitPrice Which Is Silently Ignored on Trigger | fixed | Low | |
| F-2026-1662 | createPendingOrder Silently Accepts Overpayment of SOMI, Forfeiting Excess to Admin on Trigger | fixed | Low | |
| F-2026-1662 | onEvent Does Not Gate on activeSubscriptionId | fixed | Low | |
| F-2026-1662 | placeOrderFor Accepts an Arbitrary Owner With No Per-User Consent or Opt-In Mechanism | accepted | Low | |
| F-2026-1661 | Incomplete Storage Cleanup on Pending-Order Cancellation | mitigated | Low |
Appendix 1. Definitions
Severities
When auditing smart contracts, Hacken is using a risk-based approach that considers Likelihood, Impact, Exploitability and Complexity metrics to evaluate findings and score severities.
Reference on how risk scoring is done is available through the repository in our Github organization:
Severity | Description |
|---|---|
Critical | Critical vulnerabilities are usually straightforward to exploit and can lead to the loss of user funds or contract state manipulation. |
High | High vulnerabilities are usually harder to exploit, requiring specific conditions, or have a more limited scope, but can still lead to the loss of user funds or contract state manipulation. |
Medium | Medium vulnerabilities are usually limited to state manipulations and, in most cases, cannot lead to asset loss. Contradictions and requirements violations. Major deviations from best practices are also in this category. |
Low | Major deviations from best practices or major Gas inefficiency. These issues will not have a significant impact on code execution. |
Severity
- Critical
Description
- Critical vulnerabilities are usually straightforward to exploit and can lead to the loss of user funds or contract state manipulation.
Severity
- High
Description
- High vulnerabilities are usually harder to exploit, requiring specific conditions, or have a more limited scope, but can still lead to the loss of user funds or contract state manipulation.
Severity
- Medium
Description
- Medium vulnerabilities are usually limited to state manipulations and, in most cases, cannot lead to asset loss. Contradictions and requirements violations. Major deviations from best practices are also in this category.
Severity
- Low
Description
- Major deviations from best practices or major Gas inefficiency. These issues will not have a significant impact on code execution.
Potential Risks
The "Potential Risks" section identifies issues that are not direct security vulnerabilities but could still affect the project’s performance, reliability, or user trust. These risks arise from design choices, architectural decisions, or operational practices that, while not immediately exploitable, may lead to problems under certain conditions. Additionally, potential risks can impact the quality of the audit itself, as they may involve external factors or components beyond the scope of the audit, leading to incomplete assessments or oversight of key areas. This section aims to provide a broader perspective on factors that could affect the project's long-term security, functionality, and the comprehensiveness of the audit findings.
Appendix 2. Scope
The scope of the project includes the following smart contracts from the provided repository:
Scope Details | |
|---|---|
| Repository | https://github.com/somnia-chain/dream-dex-spot-audit→ |
| Commit | 8667425efd52f68a94f0d6cae27f98bdd1b7c333 |
| Final Commit | 9015ac43a5234ddf7ecdabb35b85e4f3acbbd93b |
| Whitepaper | N/A |
| Requirements | https://github.com/somnia-chain/dream-dex-spot-audit/tree/main/docs;→ |
| Technical Requirements | README.md |
Scope Details
- Commit
- 8667425efd52f68a94f0d6cae27f98bdd1b7c333
- Final Commit
- 9015ac43a5234ddf7ecdabb35b85e4f3acbbd93b
- Whitepaper
- N/A
- Technical Requirements
- README.md
Assets in Scope
Appendix 3. Additional Valuables
Additional Recommendations
The smart contracts in the scope of this audit could benefit from the introduction of automatic emergency actions for critical activities, such as unauthorized operations like ownership changes or proxy upgrades, as well as unexpected fund manipulations, including large withdrawals or minting events. Adding such mechanisms would enable the protocol to react automatically to unusual activity, ensuring that the contract remains secure and functions as intended.
To improve functionality, these emergency actions could be designed to trigger under specific conditions, such as:
Detecting changes to ownership or critical permissions.
Monitoring large or unexpected transactions and minting events.
Pausing operations when irregularities are identified.
These enhancements would provide an added layer of security, making the contract more robust and better equipped to handle unexpected situations while maintaining smooth operations.
Frameworks and Methodologies
This security assessment was conducted in alignment with recognised penetration testing standards, methodologies and guidelines, including the NIST SP 800-115 – Technical Guide to Information Security Testing and Assessment →, and the Penetration Testing Execution Standard (PTES) →, These assets provide a structured foundation for planning, executing, and documenting technical evaluations such as vulnerability assessments, exploitation activities, and security code reviews. Hacken’s internal penetration testing methodology extends these principles to Web2 and Web3 environments to ensure consistency, repeatability, and verifiable outcomes.