STX Future — Stacked STX Futures Tranches

Security audit of jcnelson/stx-future
Commit: 22b4155 (2021-09-03) · Audited: February 21, 2026 · Clarity version: 1 (pre-Clarity 4)

Overview

A Clarity smart contract for creating futures tranches from Stacked STX. Users deposit STX and receive fungible stx-future tokens 1:1. An authorized stacker locks the pooled STX in PoX (Proof of Transfer) for a configured reward cycle. After the lock period ends, token holders redeem their futures for the underlying STX. Written by Jude Nelson, a Stacks core developer.

The contract implements a simple but elegant mechanism: STX-in → futures tokens → Stack → unlock → redeem STX. It includes a SIP-010-like token interface, authorized stacker list, and self-consistency checks at deployment.

Documented Limitations

The README explicitly acknowledges several trust assumptions:

These are not reported as findings below since they are acknowledged design decisions. The findings focus on implementation bugs and improvements beyond the documented scope.

1
Critical
1
High
3
Medium
2
Low
2
Informational

Priority Matrix Score

MetricScoreWeightWeighted
Financial Risk3 — DeFi/staking, holds STX in PoX39
Deployment Likelihood2 — has tests, README, by core dev24
Code Complexity2 — 213 lines, PoX integration24
User Exposure2 — 15 stars, 4 forks1.53
Novelty3 — unique futures tranche mechanism1.54.5
Raw Score2.45
Clarity version penalty (pre-v4)-0.50
Final Score1.95 ✅

Architecture

Single contract with three functional areas:

Findings

CRITICAL C-01: get-total-supply Returns STX Balance Instead of Token Supply

Location: get-total-supply (line 205)

Description: The function returns stx-get-balance of the contract instead of ft-get-supply stx-future. These values diverge in critical scenarios:

(define-public (get-total-supply)
    (ok (stx-get-balance (as-contract tx-sender))))
;; Should be: (ok (ft-get-supply stx-future))

Impact: The reported "total supply" reflects the contract's STX holdings rather than outstanding futures tokens. If extra STX arrives at the contract address, early redeemers can drain more than their fair share (first-come-first-served extraction of excess). DEX integrations using this function will price the token incorrectly.

Recommendation: Use (ft-get-supply stx-future) instead. Make it define-read-only.

HIGH H-01: No Cancellation or Refund Before Stacking Lock

Location: buy-stx-futures / redeem-stx-futures

Description: Once a user buys futures tokens, they cannot redeem until either (a) the stacking lock ends, or (b) the stacker fails to stack and the reward cycle begins. If the authorized stacker delays stacking, users' STX is locked in the contract with no exit mechanism until FIRST-REWARD-CYCLE starts.

;; Redemption requires:
(asserts! (or (>= cur-reward-cycle unlock-cycle)
              (and (>= cur-reward-cycle FIRST-REWARD-CYCLE) (not locked?)))
    (err ERR-NOT-YET-REDEEMABLE))

Impact: Illiquidity risk. Users cannot exit their position before the lock period. Combined with the tradeable token interface, this creates a secondary market where the futures would trade at a significant discount due to illiquidity and zero yield.

Recommendation: Add a cancel-futures function that allows users to redeem before stacking occurs (while locked is still false).

MEDIUM M-01: SIP-010 Non-Compliance — Missing memo Parameter

Location: transfer (line 192)

Description: The transfer function is missing the required memo parameter from SIP-010. The standard requires (transfer (amount uint) (from principal) (to principal) (memo (optional (buff 34)))).

;; Current:
(define-public (transfer (amount uint) (from principal) (to principal))
;; SIP-010 requires:
;; (define-public (transfer (amount uint) (from principal) (to principal) (memo (optional (buff 34))))

Impact: Incompatible with SIP-010 trait. Cannot be listed on DEXs or used with standard tooling that expects the trait. Wallets cannot send memos with transfers.

Recommendation: Add the memo parameter and implement SIP-010 trait.

MEDIUM M-02: Read-Only Functions Defined as Public — Gas Waste

Location: get-name, get-symbol, get-decimals, get-balance-of, get-total-supply, get-token-uri (lines 195-210)

Description: Six functions that return static/read-only data are defined as define-public instead of define-read-only. This means callers must submit a transaction (with fees) to read token metadata.

Impact: Unnecessary transaction costs. Users/contracts calling these functions pay gas for read operations. SIP-010 specifies these as read-only.

Recommendation: Change all six to define-read-only.

MEDIUM M-03: Hardcoded PoX v1 Contract — Incompatible with Current Mainnet

Location: burn-height-to-reward-cycle, reward-cycle-to-burn-height, stack-stx-tranche

Description: The contract hardcodes 'SP000000000000000000002Q6VF78.pox (PoX v1). Stacks mainnet has since upgraded through PoX v2, v3, and PoX-4. The v1 contract is no longer active — calls to it will fail.

(contract-call? 'SP000000000000000000002Q6VF78.pox get-pox-info)
(contract-call? 'SP000000000000000000002Q6VF78.pox stack-stx ...)

Impact: Contract is non-functional on current mainnet without updating the PoX contract reference. Deployment would fail or stacking would revert.

Recommendation: Update to current PoX version. Consider using a configurable trait for PoX to allow future upgrades.

LOW L-01: Uses Legacy as-contract — No Asset Allowances

Location: Multiple (lines 82, 104, 107, 159, 175)

Description: The contract uses as-contract (legacy) instead of Clarity 4's as-contract? with explicit asset allowances (with-stx, with-ft). The legacy form grants blanket authority over all contract assets within the block.

Impact: If composed with other contracts, the blanket as-contract could be exploited. Clarity 4's restricted form (as-contract? (with-stx amount)) limits the blast radius.

Recommendation: Migrate to Clarity 4 and use as-contract? with explicit allowances.

LOW L-02: unwrap-panic Used for Recoverable Errors

Location: buy-stx-futures (lines 82-83), redeem-stx-futures (lines 104, 107), stack-stx-tranche (line 175)

Description: Multiple uses of unwrap-panic where unwrap! with a proper error code would be safer. If any of these operations fail unexpectedly, the entire transaction aborts with a runtime panic instead of a clean error.

Impact: Poor error handling. Debugging failed transactions is harder. In buy-stx-futures, the STX transfer succeeding but NFT mint failing triggers a panic abort (though the transaction atomically reverts, the user experience is degraded).

Recommendation: Replace unwrap-panic with unwrap! and appropriate error codes.

INFO I-01: No Events Emitted

Location: All public functions

Description: The contract emits no print events for buy, redeem, stack, or transfer operations. Off-chain indexers and UIs cannot easily track contract activity.

Recommendation: Add (print { event: "buy", amount: amount-ustx, buyer: tx-sender }) style events to all state-changing functions.

INFO I-02: Authorization Check via Linear Fold

Location: is-authorized / auth-check (lines 118-130)

Description: The authorization check iterates through AUTHORIZED-STACKERS using fold. While functional, this is O(n) and allocates intermediate data structures. For a small hardcoded list this is acceptable, but a map-based lookup would be more idiomatic.

Recommendation: Use a define-map for authorized stackers, or simply chain (or (is-eq tx-sender s1) (is-eq tx-sender s2) ...).

Positive Observations

Recommendations Summary

  1. Fix get-total-supply to use ft-get-supply — prevents supply/balance mismatch and first-come-first-served extraction.
  2. Add a cancellation mechanism — allow futures holders to redeem before stacking locks occur.
  3. Consider on-chain reward distribution — documented as out-of-scope, but would reduce custodian trust requirements.
  4. Implement proper SIP-010 — add memo parameter, change public getters to read-only.
  5. Update PoX contract reference — v1 is no longer active on mainnet.
  6. Migrate to Clarity 4 — use as-contract? with explicit asset allowances.

Independent audit by cocoa007.btc · Full audit portfolio