Skip to content
Quantix Security Labs

Merivale Markets

Smart Contract Audit of Merivale Markets, a dex protocol on BNB Chain covered by Quantix Protocol Research. Order book, matching router with AMM fallback, fee controller and listing manager. 2,104 nSLOC of Solidity on BNB Chain.
ResearchUnder ReviewQX-TSA-2026-010 · v0.9
QUANTIX SECURITY LABSQX-TSA-2026-010

TECHNICAL SECURITY ASSESSMENT

Merivale Markets

Smart Contract Audit · DEX

REPORT ID QX-TSA-2026-010 · v0.9

QUANTIX RESEARCH ASSESSMENT

Order book, matching router with AMM fallback, fee controller and listing manager. 2,104 nSLOC of Solidity on BNB Chain.

Document status
Draft, under review
Review ended
11 Sept 2026

FINDINGS BY SEVERITY8 findings

Critical

0

High

3

Medium

3

Low

1

Info

1

Quantix research assessment of Merivale Markets (Protocol Research). Not a client engagement or a certification.

DRAFT
Draft QX-TSA-2026-010 v0.9 · issued when the review closes

Risk summary

State at remediation commit 5b9d0e3f7a21

Findings
8
Critical and high
3
Open or partial
4
Remediation
63%

Findings by severity

  • Critical0
  • High3
  • Medium3
  • Low1
  • Informational1

Remediation statusAnalysis

  • Resolved3
  • Partially resolved2
  • Acknowledged1
  • Open2

Merivale Markets

DEX

Document
QX-TSA-2026-010 · v0.9
Type
Smart Contract Audit
Review
17 Aug 2026 – 11 Sept 2026
Status
Remediation review in progress
Ecosystems
BNB Chain
Languages
Solidity
Risk Index
CCC61.3
Assessment contents · 12 sections

01

Assessment overview

Merivale Markets is a decentralised exchange on BNB Chain that combines an on-chain limit order book with AMM fallback liquidity. A router fills taker orders against the book and routes any remainder through external pools. Fees and listings are managed by an operator key. Quantix reviewed the order book, router, fee controller and listing manager at the commit in this report.

Three high-severity findings were identified. Token and native-coin transfers in the router ignored return values, so failed payouts could be recorded as successful; the operator key can change fees and listings instantly, including raising taker fees within the same block as a pending large order; and the batch cancellation function did not check order ownership. Three medium-severity findings concern unbounded matching loops, fee-on-transfer tokens and missing slippage protection on routed remainders.

At the time of this version the batch-cancellation and fee-on-transfer findings are fixed and verified. The unchecked-transfer and unbounded-loop findings are partially fixed, and the operator-key and slippage findings remain open. This document is therefore marked Under Review and will be reissued after the remediation review completes.

Until the open items are addressed, the protocol's safety depends heavily on the operator key: users should treat fee levels and listings as changeable without notice.

02

Technical analysis: protocol architecture

Makers place limit orders in OrderBook, which escrows the sell asset and inserts the order into a price level. Takers call Router.swap, which walks price levels from the best price, fills orders until the requested amount or the taker's limit price is reached, and routes any remaining amount through a configured AMM pool.

FeeController applies maker and taker fee tiers and sends fees to a recipient address. ListingManager decides which token pairs can trade. Both are controlled by a single operator EOA.

Trusted· 4

Assumed to behave as specified; compromise is out of the threat model.

  • OrderBookEscrow, price levels, cancellation
  • RouterMatching and AMM routing
  • FeeControllerFee tiers and recipient
  • ListingManagerPair listings
trust boundary

Semi-trusted· 2

Trusted for liveness or within bounds; misbehaviour is partly modelled.

  • Operator keyFees, listings and pausing
  • External AMM poolsFallback liquidity
trust boundary

Untrusted· 1

May act adversarially; every input is validated.

  • Makers, takers, listed tokensExternal accounts and contracts
Components grouped by trust level. Calls that cross a trust boundary are where the review concentrated input validation and authorisation checks.

Trust assumptions

  • The operator acts honestly and its key is secure (not enforced on-chain; see MM-02).
  • Listed tokens are standard BEP-20 tokens (enforced only partially; see MM-05).
  • External AMM pools behave according to their published interfaces.

03

Scope

Order book, matching router with AMM fallback, fee controller and listing manager. 2,104 nSLOC of Solidity on BNB Chain.

Repository
github.com/example-org/merivale-dex
Review effort
Two reviewers over twenty working days; remediation review in progress
Reviewed commit
e3a17c9b5d02f84a6c1e9d07b3f52a8e4c6d901f
Remediation commit
5b9d0e3f7a21c64e8f0b2d6a9c13e57f0a4b8c2d
Files and components in scope
PathDescriptionnSLOC
contracts/OrderBook.solLimit orders, escrow, cancellation and matching846
contracts/Router.solOrder matching and remainder routing through AMM pools612
contracts/FeeController.solFee tiers, rebates and fee recipient238
contracts/ListingManager.solMarket listings and token checks296
contracts/libraries/OrderLib.solOrder hashing and price-level arithmetic112
Total5 items2,104

Out of scope

  • AMM pools used for fallback liquidity (third-party code)
  • Off-chain order relayer and front end
  • Market-maker incentive distribution scripts

04

Threat model

Each threat lists the actor, the vector and the mitigation, and links to the findings that realised it in the reviewed code.

Assets at risk

  • Escrowed maker funds
  • Taker funds in transit through the router
  • Fee revenue
  • Order-book integrity

Actors

  • GrieferUntrustedCalls public functions with arbitrary inputs
  • Malicious tokenUntrustedListed token with non-standard transfer behaviour
  • Sandwich searcherUntrustedOrders transactions around takers
  • OperatorSemi-trustedChanges fees and listings instantly
Threats, vectors, mitigations and related findings
ThreatActorVectorMitigationFindings
Record a fill without paying the makerMalicious tokenIgnored transfer return valueSafeERC20 and checked native transfersMM-01
Extract value from pending orders by changing feesOperatorInstant fee change in the same blockTimelock and fee capsMM-02
Cancel other users' ordersGrieferMissing ownership check in batch cancelPer-order maker checkMM-03
Block takersGrieferMany dust orders at the best priceIteration cap and minimum order sizeMM-04
Drain router balancesMalicious tokenFee-on-transfer accountingBalance-delta accountingMM-05
Sandwich routed remainderSandwich searcherZero minimum outputPropagate a taker minimumMM-06

05

Findings

Severity

8 findings

MM-01HighPartially resolvedUnchecked external call

Router ignores return values of token transfers and native-coin refunds

contracts/Router.sol:L388–L421 (_payOut, _refund)

Likelihood
Medium
Impact
High
Weakness
CWE-252
Description

_payOut calls IERC20(token).transfer(to, amount) and discards the returned boolean. Some BEP-20 tokens signal failure by returning false instead of reverting. When such a transfer fails, the router continues as if the maker or taker had been paid, and the order is marked filled.

_refund returns excess BNB with payable(msg.sender).call{value: excess}("") and ignores the success flag. If the taker is a contract that rejects BNB, the refund silently stays in the router, where the next caller who triggers a refund path can receive it.

Tokens that do not return a value at all cause the opposite problem: the interface expects a boolean and the call reverts when decoding, blocking trading in those pairs. A safe-transfer wrapper handles both cases.

Preconditions
  • A listed token that returns false on failure, or a taker contract that rejects native refunds.
Exploit scenario
  1. 1.A token with a transfer blacklist returns false when the recipient is blacklisted.
  2. 2.A maker who is blacklisted by the token has an order filled; the transfer fails silently and the order is marked filled.
  3. 3.The proceeds remain in the router and are paid to the next taker whose payout uses the same token, while the maker loses their escrow.
Impact

Makers or takers lose funds; balances stranded in the router can be captured by other users.

Recommendation

Use SafeERC20.safeTransfer for all token movements and revert, or credit a withdrawable balance, when a native refund fails.

Fix
contracts/Router.sol (token path at remediation commit; native path recommended)diff
-      IERC20(token).transfer(to, amount);+      IERC20(token).safeTransfer(to, amount); ...-      payable(msg.sender).call{value: excess}("");+      (bool ok,) = payable(msg.sender).call{value: excess}("");+      if (!ok) pendingRefunds[msg.sender] += excess;
Resolution

Partially resolved. Token transfers now use SafeERC20. The native refund path still ignores the call result; the team plans a withdrawable refund balance.

MM-02HighOpenGovernance manipulation

Operator key can change fees and listings instantly, including within a pending taker's block

contracts/FeeController.sol:L58–L94 (setTakerFee, setFeeRecipient), contracts/ListingManager.sol:L71

Likelihood
Medium
Impact
High
Weakness
CWE-284
Description

setTakerFee(tier, bps) accepts any value up to 10,000 basis points (100%) and takes effect immediately. The operator is a single EOA. Takers specify an amount and a limit price but no maximum fee, so the fee applied is whatever the controller holds when their transaction executes.

The operator, or anyone who obtains the key, can observe a large pending swap, front-run it with a fee increase and back-run it with a reset, capturing most of the trade as fees sent to a recipient that can be changed in the same block. Listings can also be removed instantly, which blocks makers from cancelling orders in pairs that are delisted (cancellation requires an active listing).

This is a governance weakness rather than an implementation bug, but on-chain nothing limits the operator, so users must fully trust a single key.

Preconditions
  • Control of the operator key.
Exploit scenario
  1. 1.A taker submits a swap of 2 million USDT for BNB.
  2. 2.The operator front-runs with setTakerFee(0, 9000) and setFeeRecipient(attacker).
  3. 3.The taker's swap pays a 90% fee to the attacker.
  4. 4.The operator back-runs with a reset to 0.1% so that later users notice nothing unusual.
Impact

Theft of taker funds through fees and blocking of maker cancellations, limited only by the operator's willingness to act.

Recommendation

Cap fees in the contract, let takers pass a maximum fee, move fee and listing changes behind a timelock with a public delay, and ensure delisting never blocks cancellation of existing orders.

Fix
Recommended change (not yet implemented)diff
-  function setTakerFee(uint8 tier, uint16 bps) external onlyOperator {-      require(bps <= 10_000, "fee");+  uint16 public constant MAX_TAKER_FEE_BPS = 50;+  function setTakerFee(uint8 tier, uint16 bps) external onlyTimelock {+      require(bps <= MAX_TAKER_FEE_BPS, "fee cap");       takerFee[tier] = bps;   } ...-  function swap(SwapParams calldata p) external payable+  function swap(SwapParams calldata p, uint16 maxFeeBps) external payable
Resolution

Open. The team intends to introduce a 48-hour timelock and a hard fee cap in the next release.

MM-03HighResolvedAccess control

Batch cancellation does not check order ownership

contracts/OrderBook.sol:L512–L540 (cancelOrders)

Likelihood
High
Impact
Medium
Weakness
CWE-639
Description

cancelOrder(orderId) verifies msg.sender == order.maker. The batch variant cancelOrders(uint256[] ids) was added later for market makers and loops over _cancel(id) directly, skipping the check. Escrowed funds are returned to the maker, so nothing is stolen, but anyone can remove any order from the book.

The ability to clear the book at will is valuable to an attacker: removing resting liquidity just before a large taker order forces the remainder through the AMM fallback, where the attacker can sandwich it (compounded by MM-06).

Preconditions
  • None.
Exploit scenario
  1. 1.An attacker watches the mempool for a large taker order.
  2. 2.It front-runs with cancelOrders over every order on the relevant side of the book.
  3. 3.The taker order routes almost entirely through the AMM pool with a poor price, which the attacker sandwiches.
Impact

Market makers lose queue position and fills; takers suffer worse execution; the order book can be emptied repeatedly at low cost.

Recommendation

Check the maker for every order in the batch, or have both functions call a single internal function that enforces it.

Fix
contracts/OrderBook.sol (remediation commit)diff
   function cancelOrders(uint256[] calldata ids) external {       for (uint256 i; i < ids.length; ++i) {-          _cancel(ids[i]);+          if (orders[ids[i]].maker != msg.sender) revert NotMaker(ids[i]);+          _cancel(ids[i]);       }   }
Resolution

Fixed and verified; property P2 holds on the remediation commit.

MM-04MediumPartially resolvedDenial of service

Matching iterates over an unbounded number of orders, letting dust orders block takers

contracts/Router.sol:L211–L274 (_match)

Likelihood
Medium
Impact
Medium
Weakness
CWE-400
Description

_match walks the best price level order by order until the taker amount is filled. There is no minimum order size and no limit on orders visited per call. An attacker can place thousands of minimum-size orders at the best price; a taker order then consumes its gas limit filling dust and reverts, or pays very high gas for little volume.

Because orders at the best price are visited first, the attacker effectively controls access to the book for the cost of placing (and later cancelling) the dust orders.

Preconditions
  • No minimum order size; cheap order placement.
Exploit scenario
  1. 1.An attacker places 3,000 orders of one unit each at the best bid. Every taker selling into the book runs out of gas until the orders are cancelled.
Impact

Denial of service for takers on targeted markets.

Recommendation

Enforce a minimum order notional per market and cap orders visited per call, routing the remainder or returning a partial fill.

Fix
contracts/Router.sol (remediation commit)diff
-      while (remaining > 0 && level.head != 0) {+      uint256 visited;+      while (remaining > 0 && level.head != 0 && visited++ < MAX_ORDERS_PER_MATCH) {
Resolution

Partially resolved. A cap of 64 orders per match was added; a minimum order size is still pending.

MM-05MediumResolvedBusiness logic

Router credits the requested amount rather than the amount received for fee-on-transfer tokens

contracts/Router.sol:L140–L162 (_pullIn)

Likelihood
Low
Impact
High
Weakness
CWE-682
Description

_pullIn transfers amountIn from the taker and proceeds to match amountIn. For tokens that charge a fee on transfer, the router receives less than amountIn and fills the difference from tokens it already holds, such as unclaimed refunds or tokens in transit within the same transaction.

The listing manager does not reject fee-on-transfer tokens, and the router keeps transient balances, so a token designed to charge a large fee lets an attacker drain whatever the router holds in that token.

Preconditions
  • A listed token with a transfer fee.
  • Router balance in that token.
Exploit scenario
  1. 1.An attacker lists a token with a 50% transfer fee through the permissive listing path, then swaps repeatedly so the router fills the missing half from its own balance.
Impact

Loss of tokens held by the router; incorrect fills for makers.

Recommendation

Measure the balance before and after the transfer and use the difference, and reject fee-on-transfer tokens at listing.

Fix
contracts/Router.sol (remediation commit)diff
-      IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);+      uint256 before = IERC20(tokenIn).balanceOf(address(this));+      IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);+      uint256 received = IERC20(tokenIn).balanceOf(address(this)) - before;+      if (received != amountIn) revert UnsupportedToken(tokenIn);
Resolution

Fixed. Transfers that deliver less than requested revert, and listings test transfer behaviour.

MM-06MediumOpenBusiness logic

Remainder routed through AMM pools without slippage protection

contracts/Router.sol:L298–L321 (_routeRemainder)

Likelihood
Medium
Impact
Medium
Weakness
CWE-20
Description

The taker's limit price is enforced only on order-book fills. When the router sends the unfilled remainder to the AMM fallback it passes amountOutMin = 0. Any searcher can sandwich the routed portion, buying ahead of it and selling after.

The effect is largest when the book is thin, and it combines with MM-03 (before its fix) and MM-04 to push more volume to the unprotected path.

Preconditions
  • A taker order larger than the resting liquidity at acceptable prices.
Exploit scenario
  1. 1.A searcher sees a swap whose remainder will route through the AMM, front-runs it to move the pool price, and back-runs it after the router's zero-minimum trade executes.
Impact

Worse execution for takers on routed volume; value captured by searchers.

Recommendation

Apply the taker's limit price to the whole order by deriving a minimum output for the remainder, or revert if it cannot be met.

Fix
Recommended change (not yet implemented)diff
-      pool.swap(remaining, 0, address(this), "");+      uint256 minOut = remaining * p.limitPriceX18 / 1e18;+      uint256 out = pool.swap(remaining, minOut, address(this), "");
Resolution

Open.

MM-07LowAcknowledgedBusiness logic

Sequential order IDs make cancellations predictable

contracts/OrderBook.sol:L188

Likelihood
Medium
Impact
Low
Description

Order IDs increment globally. Searchers can predict which orders a market maker will cancel from its recent placements and race fills against cancellations. This is common to on-chain order books and mainly affects market makers' execution quality.

Preconditions
  • An active market maker cancelling and replacing orders.
Exploit scenario
  1. 1.A searcher fills stale quotes from a market maker just before its predictable cancellation lands.
Impact

Adverse selection for market makers.

Recommendation

Offer a cancel-all-by-nonce function so makers can invalidate quotes in one transaction.

Resolution

Acknowledged as inherent to the design; a nonce-based cancel-all is under consideration.

MM-08InformationalResolvedCode quality

Floating pragma and missing events on listing changes

contracts/*.sol

Likelihood
Low
Impact
Low
Description

Contracts use pragma solidity ^0.8.19 and the listing manager changes listings without events, which complicates monitoring of delistings.

Preconditions
  • None.
Exploit scenario
  1. 1.Not exploitable.
Impact

Compiler drift and reduced observability.

Recommendation

Pin the compiler version and emit ListingAdded and ListingRemoved.

Resolution

Fixed.

06

Severity

8findings
  • Critical0 · 0%
  • High3 · 38%
  • Medium3 · 38%
  • Low1 · 13%
  • Informational1 · 13%

By vulnerability class

  • Business logic3
  • Access control1
  • Code quality1
  • Denial of service1
  • Governance manipulation1
  • Unchecked external call1

Likelihood and impact

Each finding placed by the likelihood of its preconditions and the impact of exploitation.

Findings by likelihood (columns) and impact (rows)
Impact ↓ / Likelihood →LowMediumHigh
High
0
Medium
0
Low
0

07

Remediation

3 of 8 findings are resolved, 1 acknowledged, 2 partially resolved and 2 open at remediation commit 5b9d0e3f7a21.

Remediation progress63%

Resolved and acknowledged findings count fully; partially resolved findings count half.

63%remediated
  • Resolved3 · 38%
  • Partially resolved2 · 25%
  • Acknowledged1 · 13%
  • Open2 · 25%
Findings by severity and remediation status
SeverityResolvedPartially resolvedAcknowledgedOpenTotal
Critical00000
High11013
Medium11013
Low00101
Informational10001
All findings32128

Residual items

  • MM-01HighPartially resolvedPartially resolved. Token transfers now use SafeERC20. The native refund path still ignores the call result; the team plans a withdrawable refund balance.
  • MM-02HighOpenOpen. The team intends to introduce a 48-hour timelock and a hard fee cap in the next release.
  • MM-04MediumPartially resolvedPartially resolved. A cap of 64 orders per match was added; a minimum order size is still pending.
  • MM-06MediumOpenOpen.
  • MM-07LowAcknowledgedAcknowledged as inherent to the design; a nonce-based cancel-all is under consideration.
  • ResolvedFixed and verified in the remediation review.
  • Partially resolvedMitigated; the residual risk is documented.
  • AcknowledgedAccepted by the protocol team with a stated rationale.
  • OpenNot yet addressed at the reviewed remediation commit.

08

Formal verification

Formal verification was not part of this engagement. Two properties were checked with bounded symbolic tests to confirm findings: escrow conservation holds on the original commit, while order ownership on cancellation was violated (MM-03) and holds after the fix.

Tool: Halmos bounded symbolic tests

  • P1ProvedA fill never transfers more than the maker's escrowed amount for that order.Bounded to three price levels and four orders.
  • P2ViolatedOnly an order's maker can cancel it.Counterexample through cancelOrders on the original commit (MM-03); holds on the remediation commit.
  • P3Not in scopeFees charged never exceed the maximum fee published in documentation.No on-chain maximum exists; see MM-02.

A proved property holds for every execution of the model under its stated assumptions. Fuzzing and property-based results are evidence, not proofs, and are recorded as inconclusive.

09

Recommendations

  1. 01

    Constrain the operator on-chain

    Hard caps on fees, a timelock for parameter and listing changes, and a two-step operator transfer would remove most of the trust users currently place in a single key.

  2. 02

    Complete the refund path

    Finish MM-01 by crediting failed native refunds to a withdrawable balance and add a test with a refund-rejecting contract.

  3. 03

    Set market-level minimums

    Minimum order notionals per market complete the fix for MM-04 and reduce book spam.

  4. 04

    Protect routed volume

    Derive a minimum output for the AMM remainder from the taker's limit price (MM-06).

  5. 05

    Monitor the operator key

    Alert on every fee, recipient and listing change until a timelock is in place.

10

Methodology and limitations

  1. 01Manual review of all in-scope contracts by two reviewers
  2. 02Static analysis and triage, including unchecked return values and unbounded loops
  3. 03Invariant fuzzing of escrow conservation with standard and non-standard test tokens
  4. 04Gas profiling of matching under adversarial order-book states
  5. 05Bounded symbolic tests for cancellation and escrow properties
  6. 06Remediation review (in progress at the time of this version)

Severity classification

Critical
Direct loss of funds or permanent loss of control is achievable with little or no precondition.
High
Material loss or protocol disruption is achievable under realistic conditions.
Medium
Limited loss, degraded functionality, or impact that depends on specific conditions.
Low
Minor impact, defence-in-depth gaps, or issues with unlikely preconditions.
Informational
Code quality, documentation or best-practice observations without direct security impact.

Limitations

  • This version precedes the end of the remediation review; findings marked open or partially resolved may change status in the reissued report.
  • External AMM pools were treated as behaving according to their interfaces.
  • Gas measurements were taken on a BNB Chain fork at the block used for testing and may differ with network conditions.

No assessment can show that a system is free of vulnerabilities. This research assessment reflects the code at the commits listed. Quantix methodology

11

Technical appendix

Tools

  • FoundryUnit, fork and invariant tests with non-standard test tokens
  • SlitherStatic analysis including unchecked-transfer detectors
  • HalmosBounded symbolic tests for escrow and cancellation
  • Gas profilerMatching cost under adversarial book states
Reviewed files (SHA-256) · 4 files
SHA-256 hashes of reviewed files
FileSHA-256
contracts/OrderBook.sol1f2b107111ad882a6567de8fc60e75257b7a6e45c464e5b123b5b34726dbed71
contracts/Router.solc643f0044f986d56282f56b8b423b8d921466504edb04d8023455750b15601b3
contracts/FeeController.sol2347f8528f5b5833640bafc641235946a9f6d557b2088b19480e771cf43f4bb4
contracts/ListingManager.sol9681a756a155fd017672fb025c44982fb62a5e388834ed9823402c2faeb4a96c

Notes

  • Document version 0.9: remediation review in progress.
  • File hashes are SHA-256 digests of the reviewed sources at the audited commit.

Technical references

  1. 1.SWC-104: Unchecked call return value (opens in a new tab)
  2. 2.OpenZeppelin Contracts: SafeERC20 (opens in a new tab)
  3. 3.OpenZeppelin Contracts: Governance (TimelockController) (opens in a new tab)
  4. 4.CWE-639: Authorization Bypass Through User-Controlled Key (opens in a new tab)
  5. 5.SWC-128: DoS with block gas limit (opens in a new tab)
  6. 6.EIP-20: Token Standard (opens in a new tab)
  7. 7.CWE-682: Incorrect Calculation (opens in a new tab)
  8. 8.CWE-20: Improper Input Validation (opens in a new tab)
  9. 9.CWE-340: Generation of Predictable Numbers or Identifiers (opens in a new tab)
  10. 10.Solidity documentation: Version pragma (opens in a new tab)
  11. 11.Solidity documentation: Events (opens in a new tab)

Document QX-TSA-2026-010 is under review (version 0.9).

The remediation review is still open, so no report document has been generated. The record above reflects the state at the remediation commit; version 1.0 and its PDF are produced when the review closes.

12

Report history

  1. 17 Aug 2026Review openedReviewed commit e3a17c9b5d02 in github.com/example-org/merivale-dex
  2. 11 Sept 2026Review closedTwo reviewers over twenty working days; remediation review in progress
  3. After reviewRemediation reviewFixes checked at commit 5b9d0e3f7a21
  4. After reviewVersion 0.9 under reviewQX-TSA-2026-010: the PDF is issued when the remediation review closes

Commission an assessment written to be verified.

Share the repository and the commit you plan to deploy. We reply with scoping questions and an estimate.