Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save tinyopsstudio/5d16c2fedb3f69821c1addac88218fa8 to your computer and use it in GitHub Desktop.

Select an option

Save tinyopsstudio/5d16c2fedb3f69821c1addac88218fa8 to your computer and use it in GitHub Desktop.
Static analysis: Bitflow dlmm-swap-router-v-1-1

Static Analysis: Bitflow dlmm-swap-router-v-1-1

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

Source reviewed: https://api.hiro.so/v2/contracts/source/SM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD/dlmm-swap-router-v-1-1

Reviewer: TinyOps Studio LLC

Review date: 2026-06-02

Scope And Method

This review covers the router contract only, with a narrow supporting check of the hard-coded Bitflow core contract because this router delegates all swap execution to SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1.

The router source is 352 lines and defines eight public entrypoints, seven private helpers, and no mutable storage. I reviewed every public entrypoint, fold helper, slippage check, external call, and arithmetic update path.

Disclosure handling: I did not identify any high or critical severity issue. No mandatory private disclosure was triggered before this public submission.

1. State Model

Traits

Line Item Purpose
5 dlmm-pool-trait User-supplied pool trait used to read active bin data and call pool transfer helpers through core.
6 sip-010-trait User-supplied SIP-010 token traits passed through to core swap calls.

Constants

Lines Constant Purpose
9-19 ERR_* constants Router-local error responses for fold result, slippage, minimum amount, active-bin data, empty swap lists, result-list overflow, bin-id bounds, step bounds, and step-index slicing.
22-23 MIN_BIN_ID, MAX_BIN_ID Signed expected-bin-id range: -500 to 500.
26-27 MIN_STEPS, MAX_STEPS Simple-range swap step count bounds: 1 to 319.
30-44 STEP_INDEX_RANGE Static list of indices 0 through 319 used as a bounded fold counter for range swaps.

Mutable Stores

The router defines no data-var and no data-map. It does not mutate local state. All durable pool state and token movements occur in the delegated pool/core contracts.

2. Function Inventory

Function Lines Authority Preconditions / Asserts State Mutations External Calls / Transfers
swap-multi 48-59 Open public entrypoint. No owner/admin gate. swaps list non-empty at line 55; cumulative unfavorable bin movement must be <= max-unfavorable-bins at line 56. Each fold leg validates expected bin and per-leg minimum output in fold-swap-multi. No router-local state. Delegates to fold-swap-multi, which reads pool active bin at line 174 and calls core swap-x-for-y / swap-y-for-x at lines 178-179.
swap-x-for-y-same-multi 62-77 Open public entrypoint. swaps non-empty at line 72; cumulative unfavorable bins <= max-unfavorable-bins at line 73; total y output >= min-y-amount-total at line 74. Each active leg validates expected bin and per-leg min output. No router-local state. Uses fold-swap-x-for-y-same-multi, reads active bin at line 205, calls core swap-x-for-y at line 208.
swap-y-for-x-same-multi 80-95 Open public entrypoint. swaps non-empty at line 90; cumulative unfavorable bins <= max-unfavorable-bins at line 91; total x output >= min-x-amount-total at line 92. Each active leg validates expected bin and per-leg min output. No router-local state. Uses fold-swap-y-for-x-same-multi, reads active bin at line 243, calls core swap-y-for-x at line 246.
swap-simple-multi 98-107 Open public entrypoint. swaps list non-empty at line 104. Per-leg max-steps and min-received are enforced by the range helpers. No router-local state. Uses fold-swap-simple-multi, which dispatches to simple range helpers at lines 280-282.
swap-x-for-y-simple-multi 110-116 Open wrapper. Inherits checks from swap-x-for-y-simple-range-multi; uses MAX_STEPS. No router-local state. Calls swap-x-for-y-simple-range-multi at line 115.
swap-y-for-x-simple-multi 119-125 Open wrapper. Inherits checks from swap-y-for-x-simple-range-multi; uses MAX_STEPS. No router-local state. Calls swap-y-for-x-simple-range-multi at line 124.
swap-x-for-y-simple-range-multi 128-142 Open public entrypoint. max-steps must be 1..319 at line 134; STEP_INDEX_RANGE slice must exist at line 135; total y output must be >= min-dy at line 139. No router-local state. Folds fold-swap-x-for-y-simple-multi; each active iteration reads active bin at line 302 and calls core swap-x-for-y at line 303.
swap-y-for-x-simple-range-multi 145-159 Open public entrypoint. max-steps must be 1..319 at line 151; STEP_INDEX_RANGE slice must exist at line 152; total x output must be >= min-dx at line 156. No router-local state. Folds fold-swap-y-for-x-simple-multi; each active iteration reads active bin at line 332 and calls core swap-y-for-x at line 333.

Private Helpers

Helper Lines Notes
fold-swap-multi 161-188 Per-leg expected-bin validation, active-bin read, core swap delegation, per-leg min-received, result append, unfavorable-bin accumulation.
fold-swap-x-for-y-same-multi 190-226 Continues while remaining x input is positive, validates expected bin, delegates x-to-y swap, subtracts consumed input, accumulates output and unfavorable bins.
fold-swap-y-for-x-same-multi 228-264 Same as above for y-to-x.
fold-swap-simple-multi 266-287 Runs up to 5 independent simple-range swap records; each record supplies its own amount.
fold-swap-x-for-y-simple-multi 289-317 Uses STEP_INDEX_RANGE as a loop counter; ignores the bin-id counter argument and reads current active bin each iteration.
fold-swap-y-for-x-simple-multi 319-347 Same as above for y-to-x.
abs-int 350-352 Converts signed bin delta to unsigned unfavorable-bin count.

3. Post-Condition Coverage Matrix

The router itself does not call SIP-010 transfer, stx-transfer?, or pool-transfer. Token movement is delegated through the hard-coded core contract. In the supporting core source, swap-x-for-y sets caller to tx-sender and transfers x tokens from caller to the pool, then transfers y tokens from the pool to caller. swap-y-for-x mirrors that direction.

Router function Token movement that can occur Caller post-conditions to attach
swap-multi For each swap record, either x token is spent for y token, or y token is spent for x token, depending on x-for-y. The amount spent and received are returned per leg. For every token pair in the route, cap the maximum debit of the input token from the caller; require at least the intended output token credit. Include separate post-conditions per distinct token and pool path because the list can contain heterogeneous pools/tokens.
swap-x-for-y-same-multi One x token input budget is consumed across one or more pools; y output accumulates across successful legs. Cap total x token debit to the amount argument. Require total y token credit at or above min-y-amount-total.
swap-y-for-x-same-multi One y token input budget is consumed across one or more pools; x output accumulates across successful legs. Cap total y token debit to the amount argument. Require total x token credit at or above min-x-amount-total.
swap-simple-multi Up to five independent simple swaps. Each record supplies its own token traits, amount, direction, min received, and max steps. Attach per-record input debit caps and output minimums. Do not assume record N uses output from record N-1 unless the caller intentionally funds each record that way.
swap-x-for-y-simple-multi Single pool x-to-y swap across up to MAX_STEPS active-bin iterations. Cap x debit to x-amount; require y credit at or above min-dy.
swap-y-for-x-simple-multi Single pool y-to-x swap across up to MAX_STEPS active-bin iterations. Cap y debit to y-amount; require x credit at or above min-dx.
swap-x-for-y-simple-range-multi Single pool x-to-y swap across up to caller-selected max-steps. Cap x debit to x-amount; require y credit at or above min-dy; set max-steps low enough for the caller's intended price path.
swap-y-for-x-simple-range-multi Single pool y-to-x swap across up to caller-selected max-steps. Cap y debit to y-amount; require x credit at or above min-dx; set max-steps low enough for the caller's intended price path.

4. Authority / Access-Control Matrix

Surface Authority Model Notes
Router public entrypoints Fully open. This is appropriate for a stateless router. There are no owner ops, pause switches, admin lists, or local privileged principals in the router.
Core delegation Hard-coded to SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1. The router does not accept an arbitrary core principal, which avoids caller-selected execution cores.
Pool and token traits Caller supplied, but passed to the hard-coded core. The router does not validate the trait principals locally; core validation is the critical defense. In the supporting core source, token traits and managed-pool status are asserted before transfer.
Oracle dependencies None directly in this router. Active-bin data and pricing are read from pool/core calls.
Pause / kill switches None directly in this router. Any pool-disable or core-management behavior is external to this router.

5. Clarity Best-Practice Review

Check Result
tx-sender vs contract-caller in router No tx-sender or contract-caller usage in the router. The core uses tx-sender as the token-transfer caller; callers should use post-conditions accordingly.
unwrap-panic / unwrap-err-panic Not used in the router. Router uses unwrap! with explicit errors.
Arithmetic overflow / underflow No high-risk arithmetic found in router-local logic. Some defensive checks could make core-return assumptions explicit before subtracting consumed input, described in BF-RTR-004.
as-contract / principal escalation Not used.
Trait conformance Uses DLMM pool and SIP-010 traits. The router depends on core validation that the supplied token traits match the pool's tokens and that the pool is managed by the expected core.
State mutation No local state mutation.
Result-list bounds Uses as-max-len? on appended result lists at lines 180, 210, 248, and 283.

6. Findings Table

ID Severity Function Line Finding Recommended fix
BF-RTR-001 Low swap-simple-multi / fold-swap-simple-multi 98, 266-285 swap-simple-multi may read like a chained multi-pool route, but the helper executes up to five independent swap records and each record supplies its own amount. Output from one record is not fed into the next record. A caller or integration that assumes automatic hop chaining could overfund later legs or build weaker post-conditions. Document this function as an independent batch of simple swaps, or add a separate chained-route helper whose next input is derived from prior output. Wallet/UI integrations should label it as batch execution, not automatic path chaining.
BF-RTR-002 Low swap-x-for-y-simple-range-multi, swap-y-for-x-simple-range-multi 128-157, 289-347 The simple-range variants do not accept expected-bin-id or max-unfavorable-bins; they rely on max-steps plus minimum output. That is safe when callers set tight min-dy / min-dx, but it removes the explicit active-bin drift guard present in swap-multi and same-pair multi functions. Either document that simple-range callers must use tight min-output and post-conditions, or add optional expected-bin / unfavorable-bin bounds for parity with the other multi-swap APIs.
BF-RTR-003 Informational Core delegation from router folds 178-179, 208, 246, 303, 333 The router accepts caller-supplied pool and token traits and does not prevalidate token-pool matching before calling the hard-coded core. This is acceptable only because the core validates the supplied token traits and managed-pool status before transfer. Keep the hard-coded core principal. Document this trust boundary. If future versions support pluggable cores, require equivalent token/pool validation before any transfer path.
BF-RTR-004 Informational Same-pair and simple fold helpers 211, 249, 304, 334 The router subtracts (get in swap-result) from the remaining input without a router-local assertion that the core consumed no more than the remaining amount. With the current hard-coded core this should be an invariant and any impossible value would revert on underflow, but the router would emit a generic arithmetic failure rather than a domain-specific error. Add explicit asserts! (<= (get in swap-result) remaining-amount) checks before subtraction for clearer failure semantics and defense in depth.

Top Three Summary

  1. Low: swap-simple-multi is batch-style, not a chained multi-hop route; documentation/UI should make that explicit.
  2. Low: simple-range swaps do not expose the active-bin drift guard used by the expected-bin APIs; callers must rely on tight min-output and post-conditions.
  3. Informational: router safety depends on the hard-coded core validating caller-supplied pool/token traits before transfers; keep that trust boundary documented.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment