Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save sato820/7045abddd11131c8f8b9e71140009d66 to your computer and use it in GitHub Desktop.

Select an option

Save sato820/7045abddd11131c8f8b9e71140009d66 to your computer and use it in GitHub Desktop.
Static analysis report: Bitflow DLMM swap router v1.1 (AIBTC bounty mpwizl08f7b54c2ff179)

Static Analysis Report: Bitflow DLMM Swap Router v1.1

Target bounty: mpwizl08f7b54c2ff179

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

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

Review type: static source review only. I did not execute mainnet swaps.

Responsible disclosure note: I found no high or critical severity issue in this pass. No private disclosure was required before this public report.

1. State Model

This router has no define-data-var, define-map, local balances, local escrow, or local owner-controlled state. It is a stateless orchestration layer over:

  • the caller-supplied dlmm-pool-trait
  • caller-supplied SIP-010 token traits
  • the hard-coded DLMM core contract SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1

State can still be mutated through external calls to the DLMM core and the pool/token contracts that the core touches. Those mutations are outside this router's storage, but they are the important economic surface for callers.

Constants:

Name Value Use
ERR_NO_RESULT_DATA (err u2001) fold accumulator unwrap failure
ERR_BIN_SLIPPAGE (err u2002) accumulated unfavorable bin movement exceeds caller cap
ERR_MINIMUM_RECEIVED (err u2003) per-hop or range output below caller minimum
ERR_MINIMUM_X_AMOUNT (err u2004) same-pair Y->X total output below minimum
ERR_MINIMUM_Y_AMOUNT (err u2005) same-pair X->Y total output below minimum
ERR_NO_ACTIVE_BIN_DATA (err u2006) get-active-bin-id returned none/error
ERR_EMPTY_SWAPS_LIST (err u2007) empty public swap list
ERR_RESULTS_LIST_OVERFLOW (err u2008) result list append exceeds declared max
ERR_INVALID_BIN_ID (err u2009) expected bin is outside local min/max
ERR_INVALID_MAX_STEPS (err u2010) range max steps outside [1,319]
ERR_INVALID_STEP_INDEX_RANGE (err u2011) slice of STEP_INDEX_RANGE failed
MIN_BIN_ID -500 lower bound for expected bin IDs
MAX_BIN_ID 500 upper bound for expected bin IDs
MIN_STEPS u1 minimum range step count
MAX_STEPS u319 maximum range step count
STEP_INDEX_RANGE 0..319 bounded loop source for range swaps

Mutation model:

Store / asset surface Mutated by Authority
Router storage None N/A
DLMM pool state Calls routed through dlmm-core-v-1-1 Any caller can invoke router; actual pool/core rules decide
SIP-010 balances Calls routed through dlmm-core-v-1-1 Caller should enforce postconditions

2. Function Inventory

Public functions:

Function Authority Preconditions / asserts State mutations / external calls
swap-multi Open to any caller non-empty swaps; each expected bin checked in fold; accumulated unfavorable bins must be <= caller cap; each hop output >= min-received fold calls DLMM core swap-x-for-y or swap-y-for-x once per row
swap-x-for-y-same-multi Open non-empty swaps; total Y >= min-y-amount-total; unfavorable <= cap; each hop output >= row minimum fold calls DLMM core swap-x-for-y while remaining X > 0
swap-y-for-x-same-multi Open non-empty swaps; total X >= min-x-amount-total; unfavorable <= cap; each hop output >= row minimum fold calls DLMM core swap-y-for-x while remaining Y > 0
swap-simple-multi Open non-empty swaps; each row validates its own max-steps and min output inside helper up to 5 independent simple/range swaps
swap-x-for-y-simple-multi Open delegated to range function using MAX_STEPS calls swap-x-for-y-simple-range-multi
swap-y-for-x-simple-multi Open delegated to range function using MAX_STEPS calls swap-y-for-x-simple-range-multi
swap-x-for-y-simple-range-multi Open max-steps in [1,319]; output Y >= min-dy folds up to max-steps, repeatedly calling DLMM core swap-x-for-y while remaining X > 0
swap-y-for-x-simple-range-multi Open max-steps in [1,319]; output X >= min-dx folds up to max-steps, repeatedly calling DLMM core swap-y-for-x while remaining Y > 0

Private functions:

Function Purpose
fold-swap-multi One explicit hop with per-row token traits, expected bin, amount, minimum, and direction
fold-swap-x-for-y-same-multi Same token pair X->Y sequence with remaining input tracking
fold-swap-y-for-x-same-multi Same token pair Y->X sequence with remaining input tracking
fold-swap-simple-multi Up to 5 user-supplied simple/range swap entries
fold-swap-x-for-y-simple-multi Repeated active-bin X->Y swaps until input consumed or steps exhausted
fold-swap-y-for-x-simple-multi Repeated active-bin Y->X swaps until input consumed or steps exhausted
abs-int Converts a signed bin delta to uint

3. Post-condition Coverage Matrix

The router is open and trait-driven. Callers should rely on transaction postconditions in addition to the router's min-output checks, especially when route data comes from an aggregator or off-chain quote.

Public function Token movement surface Suggested caller postconditions
swap-multi Each row may spend the row's input token and receive the opposite token through the DLMM core For each row: at-most spend of amount for the input token; at-least receive of min-received for the output token; exact contract principals for the intended tokens/pool where supported
swap-x-for-y-same-multi At most amount X spent; total Y received across rows At-most spend amount X; at-least receive min-y-amount-total Y; constrain the token contracts to the intended X/Y pair
swap-y-for-x-same-multi At most amount Y spent; total X received across rows At-most spend amount Y; at-least receive min-x-amount-total X; constrain the token contracts to the intended X/Y pair
swap-simple-multi Up to 5 independent swap entries; not a chained route by default For each entry: at-most input spend equal to row amount; at-least output receive equal to row min-received; do not assume row N receives row N-1 output unless amounts were explicitly set that way
swap-x-for-y-simple-multi Single-pool X->Y over up to 319 active-bin steps At-most spend x-amount X; at-least receive min-dy Y; constrain pool/token contracts
swap-y-for-x-simple-multi Single-pool Y->X over up to 319 active-bin steps At-most spend y-amount Y; at-least receive min-dx X; constrain pool/token contracts
swap-x-for-y-simple-range-multi Single-pool X->Y over caller-capped steps Same as above, plus choose max-steps low enough for expected gas/route tolerance
swap-y-for-x-simple-range-multi Single-pool Y->X over caller-capped steps Same as above, plus choose max-steps low enough for expected gas/route tolerance

4. Authority / Access-control Matrix

Surface Authority model Notes
Router public functions Fully open No owner/admin gate, no pause, no kill switch
Router storage None No state to corrupt locally
Pool selection Caller-controlled trait arguments Safe use depends on caller/aggregator selecting the intended pool contract
Token selection Caller-controlled SIP-010 trait arguments Callers should use postconditions to bind expected token movements
Core swap execution Hard-coded core contract principal Good: users cannot redirect the core. Remaining risk is the pool/token trait values passed into the core
Active bin source contract-call? pool-trait get-active-bin-id Pool-supplied value is trusted for core call and unfavorable-bin accounting
Governance / fee setters Not present in router Any fee/ramp/governance authority lives in pool/core contracts, not this router

5. Clarity Best-practice Review

  • tx-sender: not used.
  • contract-caller: not used.
  • as-contract: not used.
  • unwrap-panic / unwrap-err-panic: not used.
  • unwrap!: used with explicit error constants for fold state, step slices, active bin reads, and result list append.
  • Arithmetic: unsigned subtraction assumes the core returns in <= remaining-input; signed bin delta assumes active bin IDs stay within sane bounds.
  • Trait conformance: compile-time trait types are used for pool and SIP-010 tokens.
  • Result list bounds: explicit as-max-len? guards exist for both u319 and u5 result lists.

6. Findings Table

ID Severity Function Line Finding Recommended fix
BFR-01 Low fold-swap-multi, fold-swap-x-for-y-same-multi, fold-swap-y-for-x-same-multi 174, 205, 243 The router bounds-checks expected-bin-id, but not the pool-supplied active-bin-id before computing bin-id-delta and abs-int. A malformed or unexpected pool implementation can make unfavorable-bin accounting revert or produce route semantics outside the documented [-500,500] window. The normal Bitflow pool may keep this sane, but the router accepts a trait argument and should defend the accounting boundary it documents. Add an active-bin-id bounds check before delta math, or define a separate supported active-bin window and return ERR_INVALID_BIN_ID when the pool reports outside it.
BFR-02 Low swap-simple-multi / fold-swap-simple-multi 98, 266 swap-simple-multi reads like a multi-hop route helper, but each row has its own independent amount. The function does not automatically feed row N's output into row N+1. This can lead integrators to overstate chained-route safety unless they manually set row amounts and postconditions. Document the function as "batch simple swaps" or add a distinct chained-route helper that passes output forward and checks final aggregate output.
BFR-03 Informational fold-swap-x-for-y-simple-multi, fold-swap-y-for-x-simple-multi 289, 319 The bin-id fold argument is unused; STEP_INDEX_RANGE is acting only as a bounded repeat counter while the active bin is fetched every iteration. This is likely intentional, but it is easy to misread as iterating explicit bin IDs. Rename the parameter to _step-index / _unused-step, or add a short comment explaining that each iteration re-reads the active bin after the previous core swap.
BFR-04 Informational swap-x-for-y-simple-range-multi 127 The comment says "Y for X direction" above the X-for-Y range helper. Correct the comment to "X for Y direction."
BFR-05 Informational swap-multi, swap-x-for-y-same-multi, swap-y-for-x-same-multi, swap-simple-multi 52, 68, 86, 102 Empty-list validation happens after fold. With an empty list this does not mutate state, but validating user shape before invoking the fold is clearer and reduces audit burden. Move asserts! (> (len swaps) u0) before the fold in each public function.
BFR-06 Low swap-x-for-y-same-multi, swap-y-for-x-same-multi 75, 93 The response returns total output and results, but not the unspent input when a route consumes less than the supplied amount. Integrators can derive this by summing in values, but the API makes leftover handling less explicit. Include remaining-x-amount / remaining-y-amount, or include input-spent, in the top-level response.

Overall assessment: the router is small, stateless, and mostly careful about explicit min-output checks and list bounds. The main residual risk is integration risk: callers supply pool/token traits and must attach strong postconditions; simple/batch route naming should be documented carefully so users do not assume automatic chaining.

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