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.
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:
- same-pair routes stop validating trailing pool descriptors once input is exhausted;
- heterogeneous batch results contain only
{in, out}, so they are not self-describing; - the "simple" family has no active-bin drift bound and relies entirely on output floors;
- 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.
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
| 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 |
| 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 |
| 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 |
| 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 |
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.
| 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 |
- No
tx-sender,contract-caller, oras-contractuse appears in the router. - No
unwrap-panicappears; failures usetry!,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-idargument is an unused loop counter, not a bin ID.
| 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. |
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.
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.
- The fixed core rejects mismatched pool/token traits before transfer.
- The fixed core never reports
ingreater than the amount supplied. - SIP-010 implementations used by callers follow expected transfer semantics.
- Pool
get-active-bin-idvalues remain within the protocol's intended domain. - Callers set meaningful nonzero output floors and deny-mode post-conditions.
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.