Skip to content

Instantly share code, notes, and snippets.

@Fernando-droidx
Created June 13, 2026 19:45
Show Gist options
  • Select an option

  • Save Fernando-droidx/c2c1cde195f2d7cfb6039e25ae5bdfa3 to your computer and use it in GitHub Desktop.

Select an option

Save Fernando-droidx/c2c1cde195f2d7cfb6039e25ae5bdfa3 to your computer and use it in GitHub Desktop.
Bitflow DLMM swap-router v1.1 static analysis

Static Analysis: Bitflow DLMM Swap Router v1.1

Contract: SM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD.dlmm-swap-router-v-1-1

Source reviewed: Hiro mainnet contract source, 352 lines, fetched 2026-06-13.

Scope: the router contract only. The hard-coded core, pool implementations, and SIP-010 token implementations are trust dependencies, not re-audited here.

Executive Summary

The router is stateless and has no owner/admin surface. Its central safety property is atomic delegation to a fixed DLMM core plus caller-selected output floors. I found no high or critical issue in the router itself.

The most actionable weaknesses are integration hazards:

  1. same-pair routes stop validating trailing pool descriptors once input is exhausted;
  2. heterogeneous batch results contain only {in, out}, so they are not self-describing;
  3. the "simple" family has no active-bin drift bound and relies entirely on output floors;
  4. arithmetic trust in the fixed core is implicit and produces generic underflow failures if violated.

No responsible-disclosure trigger was reached because no high/critical finding is included.

1. State Model

Durable State

None. The router defines:

  • no data-var;
  • no data-map;
  • no fungible or non-fungible token;
  • no owner, pause flag, upgrade pointer, or allowlist.

All durable state mutations and token transfers occur through the hard-coded core:

SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1

Constants

Constant group Values Purpose
Domain errors ERR_NO_RESULT_DATA through ERR_INVALID_STEP_INDEX_RANGE Typed router failures
Bin bounds MIN_BIN_ID = -500, MAX_BIN_ID = 500 Bounds caller-provided expected bin IDs
Step bounds MIN_STEPS = 1, MAX_STEPS = 319 Bounds simple-range traversal
STEP_INDEX_RANGE integer list 0..319 Supplies fold iterations; values are not used as bin IDs

Mutation Boundaries

Surface Mutation
Router None
Pool trait get-active-bin-id is read-only
Fixed core Executes swaps and token movement
Caller wallet Pays input token and receives output token through core

2. Function Inventory

Public Functions

Function Authority Main Preconditions External Calls / Effects
swap-multi (48) Open Non-empty list; each executed expected bin within [-500,500]; each output meets per-leg minimum; aggregate unfavorable delta within caller limit Reads every listed pool's active bin; calls fixed core once per leg
swap-x-for-y-same-multi (62) Open Non-empty; executed expected bins valid; per-leg and total output floors; aggregate unfavorable delta limit Reads/calls pools in order until input is exhausted
swap-y-for-x-same-multi (80) Open Symmetric to X-for-Y Reads/calls pools in order until input is exhausted
swap-simple-multi (98) Open Non-empty; each leg has 1..319 steps; each leg meets its output floor Executes up to five independent simple-range swaps
swap-x-for-y-simple-multi (110) Open Inherits range checks; output at least min-dy Calls X-for-Y range variant with 319 steps
swap-y-for-x-simple-multi (119) Open Inherits range checks; output at least min-dx Calls Y-for-X range variant with 319 steps
swap-x-for-y-simple-range-multi (128) Open max-steps in 1..319; total output floor Reads active bin and calls fixed core until input exhausted or steps consumed
swap-y-for-x-simple-range-multi (145) Open Symmetric to X-for-Y Same, reverse direction

Private Functions

Function Role Important Behavior
fold-swap-multi (161) Executes independent expected-bin legs Always validates and executes every supplied leg
fold-swap-x-for-y-same-multi (190) Distributes one X amount across same-pair pools Stops validating/executing descriptors after remaining input reaches zero
fold-swap-y-for-x-same-multi (228) Reverse-direction equivalent Same early-stop behavior
fold-swap-simple-multi (266) Executes up to five independent simple-range swaps Results contain amounts only
fold-swap-x-for-y-simple-multi (289) Iterates active bins Fold counter is unused; active bin is fetched every iteration
fold-swap-y-for-x-simple-multi (319) Reverse-direction equivalent Same behavior
abs-int (350) Converts signed bin delta to uint Used for unfavorable-delta accumulation

3. Post-Condition Coverage Matrix

Use post-condition mode deny so any undeclared token movement aborts.

Function family Expected Movement Recommended Post-Conditions
swap-multi Each independent leg spends its specified input token and receives its specified output token Cap every input-token spend and require every expected output-token receipt; do not infer token identity from returned {in,out} tuples
same-pair multi Spends up to amount of one token; receives aggregate opposite token Cap input spend by amount; require output receipt at least the total minimum
swap-simple-multi Up to five independent, potentially heterogeneous swaps Cap every leg's input token and require every leg's output token; keep an off-chain index-to-token map
simple single/range Spends up to supplied amount and receives opposite token Cap input spend and require output at least min-dx/min-dy

The router's output floors revert atomically, but transaction post-conditions remain necessary because token and pool traits are caller-selected.

4. Authority / Access-Control Matrix

Surface Authority Assessment
Public router functions Any caller Appropriate for a stateless router
Core selection Immutable hard-coded principal Strongly limits delegation risk, but all router safety depends on this core
Pool selection Caller-supplied trait Core must verify pool legitimacy and token/pool matching
Token selection Caller-supplied SIP-010 traits Caller and core must reject malicious/mismatched traits
Pause / owner / upgrade None No privileged router operation exists

5. Clarity Best-Practice Review

  • No tx-sender, contract-caller, or as-contract use appears in the router.
  • No unwrap-panic appears; failures use try!, unwrap!, and typed router errors.
  • Arithmetic subtraction of core-reported consumption is not guarded locally. A violated core invariant aborts atomically, but with a generic arithmetic failure.
  • Empty-list assertions run after folds. This is harmless because an empty fold has no effects, but pre-fold guards would be clearer.
  • The 319-element constant fold range is explicit and bounded.
  • The simple fold's bin-id argument is an unused loop counter, not a bin ID.

6. Findings Table

ID Severity Function / Line Finding Recommended Fix
DO-BF-01 Low same-pair folds, 190-263 Once the shared input is exhausted, trailing descriptors are returned untouched without validating their expected-bin-id, reading their pools, or checking their per-leg minimums. A route can therefore succeed even if trailing descriptors are malformed. This is atomic and does not lose funds, but it weakens route-validation guarantees and can hide stale route data. Validate descriptor shape before the execution fold, or explicitly document that trailing descriptors are ignored after full fill.
DO-BF-02 Low swap-multi and swap-simple-multi, 48-107 Returned result entries contain only {in, out}. For heterogeneous batches, the response does not identify pool, direction, or token pair. Indexers must retain the original request and assume positional correspondence. Return pool/direction identifiers with each result, or emit a structured event per leg.
DO-BF-03 Low simple family, 98-159 Simple-range routes do not accept expected-bin-id or an unfavorable-bin budget. They remain protected by output floors, but integrations cannot independently bound active-bin drift between quote and execution. Document the distinction and require tight nonzero output floors; optionally add expected-bin/drift variants.
DO-BF-04 Low same-pair folds, 211 and 249 The router subtracts core-reported in from remaining input without an explicit in <= remaining assertion. The fixed core is trusted to preserve this invariant; if it does not, the transaction aborts with a generic arithmetic error rather than a router-domain error. Add a typed invariant assertion before subtraction.
DO-BF-05 Informational simple folds, 289 and 319 The bin-id fold argument is unused; the active bin is queried on every iteration. The name suggests it controls bin traversal when it is only an iteration counter. Rename to _step and document that the pool/core decides the next active bin.
DO-BF-06 Informational public entrypoints, 48-159 Empty-list assertions execute after folds. No external call occurs for an empty list, so behavior is safe, but the ordering makes review harder. Move empty-list assertions before fold evaluation.

Finding Detail

DO-BF-01: Trailing Same-Pair Descriptors Are Not Validated

Both same-pair fold functions branch on the remaining input before constructing the inner let. When the amount reaches zero, the code returns result-data immediately. Consequently, all fields in later descriptors, including expected-bin-id, are ignored.

This differs from swap-multi, which executes and validates every descriptor. An integrator using successful execution as proof that a complete route plan is fresh can receive a false positive for the unused suffix.

Impact is limited because ignored descriptors cause no token movement. The fix is primarily about deterministic validation and observability.

DO-BF-02: Heterogeneous Results Are Not Self-Describing

swap-multi and swap-simple-multi allow each leg to select different pool/token traits and direction, but append only the fixed core result {in, out}. If request context is lost, an indexer cannot reconstruct which asset each number represents from the response alone.

This does not affect atomic execution. It increases integration and accounting risk, especially for generic batch relayers.

Trust Assumptions

  1. The fixed core rejects mismatched pool/token traits before transfer.
  2. The fixed core never reports in greater than the amount supplied.
  3. SIP-010 implementations used by callers follow expected transfer semantics.
  4. Pool get-active-bin-id values remain within the protocol's intended domain.
  5. Callers set meaningful nonzero output floors and deny-mode post-conditions.

Conclusion

The router is compact, stateless, and easy to reason about. Its primary risks are not privilege escalation but ambiguous integration semantics at the edges of batch and partial-fill behavior. The recommended changes improve route validation, response attribution, and failure clarity without changing the fixed-core trust model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment