ALEX alex-staking-v2

Token migration wrapper for ALEX staking — converts token-alex ↔ age000-governance-token and delegates to reserve pool

ContractSP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.alex-staking-v2
ProtocolALEX — leading DEX on Stacks
SourceVerified on-chain via Hiro API (/v2/contracts/source)
Clarity Version2 (pre-Clarity 4 — uses as-contract without asset allowances)
Lines of Code104
Confidence🟡 MEDIUM — thin wrapper; security depends heavily on external token contracts and reserve pool not reviewed here
DateFebruary 24, 2026
0
Critical
1
High
2
Medium
2
Low
3
Informational

Overview

This contract is a thin migration wrapper in the ALEX staking system. It serves two purposes:

  1. Token swap on stake: Burns token-alex from the user, mints age000-governance-token to the user, then stakes via the reserve pool.
  2. Token swap on claim: Claims rewards from the reserve pool, burns age000-governance-token, and mints token-alex back to the user.

The contract also implements the ALEX DAO extension and proposal traits, allowing it to be enabled/disabled via governance. Its execute function disables alex-staking (v1) and enables itself (v2).

Architecture

Documented Limitations

Priority Score

MetricScoreWeightWeighted
Financial risk3 (DeFi staking)39
Deployment likelihood3 (deployed mainnet)26
Code complexity1 (104 lines, thin wrapper)22
User exposure3 (ALEX is largest DEX on Stacks)1.54.5
Novelty1 (similar staking category)1.51.5
Final Score2.3 / 3.0 (−0.5 Clarity version penalty → 1.8 ✅)

Findings

HIGH

H-01: Blocklist bypass via direct reserve pool interaction

Location: stake-tokens, claim-staking-reward

Description: The blocklist only applies to users who interact through this contract. Since the underlying alex-reserve-pool is a separate deployed contract, blocklisted users can bypass the restriction by calling the reserve pool directly (or through another authorized extension), completely defeating the blocklist mechanism.

(define-public (stake-tokens (amount-token uint) (lock-period uint))
  (let ((sender tx-sender))
    (asserts! (not (is-blocklisted-or-default sender)) ERR-NOT-AUTHORIZED)
    ;; ... user can skip this contract entirely

Impact: Blocklisted addresses (e.g., sanctioned entities) can still stake and claim rewards by interacting with the reserve pool directly, unless that contract also enforces its own blocklist.

Recommendation: The blocklist should be enforced at the reserve pool level, not just at this wrapper. Alternatively, the reserve pool should restrict which contracts can call its staking functions to ensure all interactions flow through blocklist-enforcing wrappers.

MEDIUM

M-01: Blocklist check occurs after reserve pool state change in claim flow

Location: claim-staking-reward, lines 67-75

Description: In claim-staking-reward, the reserve pool's claim-staking-reward is called inside the let binding — before the blocklist assertion. While the entire transaction reverts on assertion failure (so no funds are lost), this ordering wastes gas for blocklisted users and is inconsistent with stake-tokens which checks the blocklist first.

(define-public (claim-staking-reward (target-cycle uint))
  (let (
    (sender tx-sender)
    ;; Reserve pool claim executes FIRST
    (claimed (try! (contract-call? ... claim-staking-reward ...)))
    (total-claimed (+ (get to-return claimed) (get entitled-token claimed))))
    ;; Blocklist check happens AFTER
    (asserts! (not (is-blocklisted-or-default sender)) ERR-NOT-AUTHORIZED)
    ...

Impact: No direct security impact (transaction reverts fully), but violates fail-fast principle and wastes gas for blocklisted users.

Recommendation: Move the blocklist check before the let block or make it the first binding that can short-circuit.

MEDIUM

M-02: claim-staking-reward-many silently includes individual failures

Location: claim-staking-reward-many, line 77

Description: The batch claim function uses map over claim-staking-reward and wraps the result in (ok ...). Since each individual claim returns a response type, failures for specific cycles are captured as (err ...) values within the list but the outer call still succeeds. Users may not realize some claims failed.

(define-public (claim-staking-reward-many (target-cycles (list 1000 uint)))
  (ok (map claim-staking-reward target-cycles)))

Impact: Users calling this function may believe all claims succeeded when some silently failed. No funds are lost, but claimed amounts may be less than expected without clear indication.

Recommendation: Consider using fold to accumulate results and revert on any failure, or return a count of successful vs failed claims.

LOW

L-01: No lock-period bounds validation

Location: stake-tokens, line 59

Description: The lock-period parameter is passed directly to the reserve pool without any local validation. While the reserve pool presumably validates this, defense-in-depth would catch invalid values earlier with a more descriptive error.

Impact: Minimal — depends on reserve pool validation. Users may get confusing error codes from the downstream contract.

Recommendation: Add a local assertion for valid lock period range (e.g., (> lock-period u0) and (<= lock-period u32)) before delegating.

LOW

L-02: Extension status dependency creates silent failure mode

Location: stake-tokens, claim-staking-reward

Description: The contract uses as-contract to call burn-fixed and mint-fixed on the token contracts. These calls succeed only if this contract is an authorized extension/minter for both .token-alex and age000-governance-token. If the contract loses extension status on either token (e.g., via a DAO governance action), all staking and claiming operations silently break.

Impact: If extension status is revoked for either token, users cannot stake or claim. Staked funds would need to be recovered through the reserve pool directly.

Recommendation: Document the extension dependency clearly. Consider adding a read-only function that checks extension status on both tokens so users/UIs can verify operability.

INFORMATIONAL

I-01: Pre-Clarity 4 — uses as-contract without asset allowances

Description: The contract uses as-contract (Clarity 2) which gives blanket authority over all contract-held assets. Clarity 4 introduced as-contract? with explicit asset allowances (with-ft, with-nft, with-stx) that would restrict what the contract can do when acting as itself.

Recommendation: If redeploying, migrate to Clarity 4 and use as-contract? with explicit with-ft allowances for token-alex and age000-governance-token.

INFORMATIONAL

I-02: execute() is a one-shot migration proposal

Description: The execute function disables v1 staking and enables v2. This is a standard DAO proposal pattern but it means the contract doubles as both a proposal and an extension. Once executed, the function has no further purpose but remains callable (though re-execution is harmless — it re-sets the same state).

(define-public (execute (sender principal))
  (begin 
    (try! (contract-call? .executor-dao set-extensions
      (list { extension: .alex-staking, enabled: false })))
    (try! (contract-call? .executor-dao set-extensions
      (list { extension: .alex-staking-v2, enabled: true })))
    (ok true)))

Recommendation: No action needed — this is standard ALEX DAO convention.

INFORMATIONAL

I-03: Admin functions delegate to reserve pool with full DAO authority

Description: Functions like set-reward-cycle-length, set-token-halving-cycle, and set-coinbase-amount are DAO-gated and use as-contract to call the reserve pool. This means the DAO can modify fundamental staking parameters (cycle length, emission schedule) through this contract. These are powerful operations that affect all stakers.

Recommendation: These are standard DAO governance controls. Users should be aware that staking parameters can change via governance votes. No code change needed.

Positive Observations

Summary

This is a well-structured thin wrapper contract with minimal attack surface. The most significant finding is the blocklist bypass (H-01) — the blocklist is only enforced at this wrapper layer and can be circumvented by interacting with the reserve pool directly. The remaining findings are ordering issues and defense-in-depth improvements. The contract's security ultimately depends on the underlying alex-reserve-pool and token contracts, which were not in scope for this audit.