Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save silentgeckoaudit3801/1f95ed3532f9ea1fdd948de76f21f7f7 to your computer and use it in GitHub Desktop.

Select an option

Save silentgeckoaudit3801/1f95ed3532f9ea1fdd948de76f21f7f7 to your computer and use it in GitHub Desktop.
Static analysis: stSTX-STX stableswap v1.2

Static Analysis: stSTX-STX Stableswap v1.2

Contract: SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stableswap-stx-ststx-v-1-2
Audit date: 2026-06-08
Scope: Deployed source returned by the Hiro mainnet contract-source API.

Executive summary

The contract custodies STX and stSTX, issues LP shares through a caller-supplied LP trait, calculates a two-asset stable invariant, and distributes swap fees to LP rewards, StackingDAO, and Bitflow.

The most consequential defect is an inverted fee-selection condition in both swap directions: ordinary users receive the zero-valued admin fee schedule, while admins receive the normal fee schedule. The review also identified a governance authorization pattern that exposes every admin capability through intermediary-contract phishing, and inconsistent amplification handling between swap math and LP accounting.

No critical or high-severity issue was identified. The fee bypass affects protocol and LP revenue rather than user principal, and the governance issue requires an admin to initiate a transaction through an untrusted contract.

Severity Count
Critical 0
High 0
Medium 4
Low 2
Informational 2

1. State model

Constants

Constant Line Purpose
this-contract 20 Contract principal captured through as-contract
deployment-height 23 Burn height used as cycle epoch
cycle-length 26 144-block reward-accounting cycle
index-list 29 384 Newton-Raphson iterations
number-of-tokens 32 Two-token invariant parameter
contract-deployer 35 Immutable original deployer and non-removable admin

Data variables

Store Line Initial value Writers / authority
staking-and-rewards-contract 47 deployer set-staking-contract; any admin, once
staking-and-rewards-contract-is-set 50 false set-staking-contract; any admin
stacking-dao-contract 53 fixed principal set-stacking-dao-contract; any admin
bitflow-contract 56 fixed principal set-bitflow-contract; any admin
admins 59 deployer add-admin, remove-admin; any admin
buy-fees 62 3/0/2 bps change-buy-fee; any admin
sell-fees 65 3/195/2 bps change-sell-fee; any admin
admin-swap-fees 68 0/0/0 bps change-admin-swap-fee; any admin
liquidity-fees 71 3 bps change-liquidity-fee; any admin
helper-principal 74 deployer Temporary writer in remove-admin
convergence-threshold 77 u2 change-convergence-threshold; any admin

Maps

Map Lines Fields Writers
PairsDataMap 84-93 approval, shares, decimals, balances, D, amplification create, swaps, LP operations, approval and A setters
CycleDataMap 95-97 per-pair/cycle LP fee balance in STX both swap directions

PairsDataMap is also the contract's internal reserve ledger. Actual STX and stSTX custody remains at this contract principal; LP supply and balances live in the supplied LP-token contract.

2. Function inventory

Read-only and private functions

Group Functions Behavior
State/cycle reads get-pair-data, get-cycle-data, get-current-cycle, get-cycle-from-height, get-starting-height-from-cycle, get-deployment-height Open state and cycle queries
Quotes get-dx, get-dy Quotes Y-to-STX and STX-to-Y using normal fee schedules
Invariant math get-x, get-y, get-D Newton-Raphson stable-invariant calculations
Fold helpers x-for-loop, y-for-loop, D-for-loop Up to 384 iterations; return u0 convergence sentinel on failure
Decimal helpers get-scaled-up-token-amounts, get-scaled-down-token-amounts Normalize token precision
Governance helper is-not-removeable Filters the selected admin from the list

Public functions

Function(s) Lines Authority / preconditions Mutations / transfers
swap-x-for-y 321-439 Open; pair approved; input less than 10x reserve; output strictly above minimum STX from caller; stSTX to caller; three fee transfers; pair/cycle maps
swap-y-for-x 443-557 Open; pair approved; input less than 10x reserve; output strictly above minimum stSTX from caller; STX to caller and fee recipients; pair/cycle maps
add-liquidity 568-680 Open; approved pair; positive input; D and LP minimum checks STX/stSTX in, fees to Bitflow, LP mint, pair map
withdraw-liquidity 685-738 Open; strict token minimums; notably does not enforce pair approval LP burn, STX/stSTX out, pair map
create-pair 865-909 Any admin; unique pair; scaled balances equal and not both zero Initial custody, LP mint, pair map
set-pair-approval 915-932 Any admin Pair approval
add-admin, remove-admin 937-981 Any admin; deployer cannot be removed Admin list and helper var
Fee setters 984-1033 Any admin; no numeric bounds Fee variables
change-amplification-coefficient 1037-1055 Any admin; no bound or ramp Pair A parameter
change-convergence-threshold 1058-1068 Any admin; no bound Global math threshold
Destination setters 1072-1124 Any admin; staking destination only once Fee/reward destinations

3. Post-condition coverage matrix

Public function Token movement Recommended caller post-conditions
swap-x-for-y STX leaves caller; stSTX enters caller Cap STX sent; require minimum stSTX received; bind exact token and pool
swap-y-for-x stSTX leaves caller; STX enters caller Cap stSTX sent; require minimum STX received
add-liquidity STX/stSTX leave caller; LP enters caller Cap both inputs and require minimum LP received
withdraw-liquidity LP burns; STX/stSTX enter caller Cap LP burn and require minimum receipts for both assets
create-pair Initial STX/stSTX leave admin; LP enters admin Cap both deposits and require expected LP receipt
Governance functions No direct caller token movement Bind exact contract/function/arguments; admins must avoid intermediary contracts

Minimum arguments use strict > checks rather than >=. A transaction whose output exactly equals the caller's stated minimum will revert.

4. Authority / access-control matrix

Capability Intended authority Actual check
Swap / add / withdraw Any caller meeting pair/math checks Open
Create or approve pair Admin Membership of original tx-sender in admins
Add/remove admin Admin Membership of original tx-sender in admins
Change fees, A, threshold, destinations Admin Membership of original tx-sender in admins
Remove deployer Nobody Explicitly prohibited
Withdraw from disapproved pair Any LP holder Allowed; approval value is read but not asserted

All admin checks rely only on tx-sender; no admin function checks contract-caller. Therefore an untrusted contract called by an admin can invoke the complete governance surface with that admin's authority.

5. Clarity best-practice review

Check Result
tx-sender vs contract-caller Every governance function trusts tx-sender; see SS-02
Panic unwraps No unwrap-panic or unwrap-err-panic found
Arithmetic overflow / underflow Stable-invariant multiplication is large and all fee/A/threshold setters are unbounded; malformed privileged settings can abort paths
as-contract / principal escalation Necessary for custody transfers and LP minting, but it magnifies the impact of caller-supplied trait behavior and governance compromise
Trait conformance SIP-010 and LP traits are used. Pair creation is admin-gated, containing arbitrary-trait risk
Precision / rounding Decimal scaling floors on scale-down. Quote/execution fee behavior diverges for ordinary users because of SS-01
Convergence Math returns a zero sentinel after 384 unsuccessful iterations and callers do not reject it; see SS-04
Amplification governance Immediate, unbounded changes with no ramp or timelock; swap and LP math pass A differently; see SS-03

6. Findings table

ID Severity Function Line Finding Recommended fix
SS-01 Medium Both swaps 332-343, 454-465 The admin fee condition is inverted. When the caller is an admin the contract selects normal buy-fees/sell-fees; ordinary users receive admin-swap-fees, initialized to zero. Regular swaps therefore bypass LP, StackingDAO, and Bitflow fees. Swap the branches so admins receive admin-swap-fees and all other callers receive the directional normal schedule. Add tests for admin and non-admin callers in both directions.
SS-02 Medium All governance functions 865-1122 Admin authority is exposed through tx-sender. An intermediary contract called by any admin can add itself as admin, change fee destinations, alter fees/A/threshold, and approve pairs because the original admin remains tx-sender. Require contract-caller to be an admin. For direct-wallet-only governance, require both tx-sender and contract-caller to be the same admin. Prefer a timelocked governance contract.
SS-03 Medium LP operations vs swaps 357, 415, 475, 533, 596-612, 706 Swap and LP accounting use different effective amplification values. Swaps and quotes call invariant helpers with A * number-of-tokens, while add/withdraw liquidity call get-D with raw A. LP share minting and stored D can therefore be calculated on a different curve than swap pricing. Define one canonical Ann helper and use it in every call to get-D, get-x, and get-y. Recompute expected LP mint/burn values in regression tests.
SS-04 Medium get-x, get-y, get-D callers 177-307, 749-797 Non-convergence silently becomes u0. Fold helpers leave converged at zero after 384 iterations; callers consume that value as a valid reserve/invariant. In unfavorable inputs this can produce nonsensical quotes or abort later arithmetic instead of a structured failure. Return a response from invariant helpers and reject converged == u0 with a dedicated error before using the value.
SS-05 Low Fee/A/threshold setters 984-1066 Critical numeric governance settings have no local bounds. Fee sums can exceed 100%, A can be zero or changed abruptly, and threshold can accept coarse convergence. Accidental or compromised settings can brick or distort pricing. Enforce component and total fee caps, a positive bounded A with gradual ramping, and a conservative threshold range.
SS-06 Low Quote functions 141-176, 225-260 Quotes always apply normal fees while ordinary execution currently applies the zero admin schedule. Integrators receive a materially different quote from current execution, particularly on the 195 bps sell side. Fix SS-01, then expose caller-aware quote variants or clearly document that quotes assume the normal schedule.
SS-07 Informational User minimum checks 376, 494, 636, 710, 713 Minimums use strict > instead of >=, causing transactions that exactly meet their stated bound to revert. Use >= unless the stricter semantic is deliberate and documented.
SS-08 Informational withdraw-liquidity 689-713 The function reads current-approval but never asserts it. This permits exits after a pair is disabled, which is usually desirable but differs from swaps/additions and should be explicit. Document exit-only behavior for disapproved pairs or add an intentional named policy check.

SS-01 impact detail

The comments state "Admins pay no fees on swaps," but the conditional branches do the opposite. With the deployed initial state, a non-admin STX-to-stSTX swap selects {lps: u0, stacking-dao: u0, bitflow: u0} rather than the 5 bps buy schedule. A non-admin stSTX-to-STX swap similarly bypasses the configured 200 bps sell schedule, including the 195 bps StackingDAO component.

This is rated medium because it causes systematic loss of expected fee revenue and LP incentives without directly taking user principal. No private responsible-disclosure action was required for this severity.

SS-02 impact detail

In Clarity, tx-sender remains the original transaction initiator across contract calls, while contract-caller identifies the immediate caller. If an admin wallet calls a malicious contract, that contract can call add-admin while the stableswap still sees the wallet admin as tx-sender. Once added, the attacker principal can invoke governance directly.

The issue requires an admin to initiate a call through attacker-controlled code and is therefore rated medium rather than high.

Positive security properties

  • Pair creation and arbitrary trait selection are admin-gated.
  • Pair uniqueness is enforced.
  • Swaps and additions require pair approval.
  • Swap input has a reserve-relative cap and user-provided minimum output.
  • Multi-contract transfer and accounting workflows remain atomic on error.
  • The deployer cannot be removed from the admin set.
  • All asset transfers use structured unwrap! errors.
  • No high or critical issue was found, so mandatory private disclosure was not triggered before publication.

Suggested tests

  1. Execute both swap directions as an admin and a normal user; assert the exact recipient fee amounts after correcting SS-01.
  2. Have an admin call a mock intermediary that invokes add-admin and each setter; verify current success and proposed rejection.
  3. Property-test LP mint/burn and swap invariants using one canonical Ann.
  4. Force non-convergent or zero-convergence math and verify a structured error.
  5. Fuzz fee sums, A, threshold, decimals, reserves, and minimum-output edges.
  6. Verify documented exit behavior after set-pair-approval false.
@solar-flare99

Copy link
Copy Markdown

This is nice! We built immunity-agent to ensure developers and agents write code securely, anyone can use it to have better package decisions, secret redaction and prompt injection! Would love community feedback and contribution

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