ALEX Governance Token — Security Audit

SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token
Audited: 2026-02-25 · Clarity version: pre-Clarity 4 · Confidence: High

Overview

AGE000 Governance Token is the core governance token contract for the ALEX DEX ExecutorDAO system — the largest decentralized exchange on Stacks. It implements SIP-010 fungible token traits alongside a DAO-governed extension interface for minting, burning, locking, and transferring tokens.

Source: On-chain (Hiro Explorer) · Hiro API

Architecture: The contract defines two fungible tokens: alex (transferable) and alex-locked (non-transferable locked variant). Access control is dual-layered: the ExecutorDAO and its extensions can call privileged edg-* functions, while a separate approved-contracts map grants mint/burn access to specific contracts. Standard SIP-010 transfer is available to token holders. A fixed-point math layer (8-decimal precision) wraps core operations for protocol-level convenience.

Priority Score

MetricScoreWeightWeighted
Financial risk339
Deployment likelihood326
Code complexity224
User exposure31.54.5
Novelty21.53
Raw Score2.65
Clarity version penalty (pre-Clarity 4)-0.50
Final Score2.15 ✓

Findings Summary

0
Critical
0
High
3
Medium
3
Low
3
Informational

Medium Findings

M-01 — Privileged functions can burn tokens from any owner without consent

Location: edg-burn, burn

Description: The edg-burn function accepts an arbitrary owner parameter and burns tokens from their account. It only verifies the caller is the DAO or an approved contract — it does not check that the owner consented. Any approved contract or DAO extension can burn tokens from any holder.

(define-public (edg-burn (amount uint) (owner principal))
  (begin
    (asserts! (or (is-ok (is-dao-or-extension)) (is-ok (check-is-approved))) err-unauthorised)
    (ft-burn? alex amount owner)
  )
)

Impact: A compromised or malicious approved contract could burn tokens from arbitrary users. While this is gated behind DAO governance, the lack of owner consent is a trust assumption that should be explicit.

Recommendation: This is by design for DAO governance tokens (the DAO needs slashing/burning capability). However, consider adding an explicit sender-is-owner check on the public burn wrapper, while keeping the DAO-only edg-burn unrestricted. Alternatively, document this trust assumption prominently.

M-02 — edg-transfer allows moving tokens between arbitrary accounts

Location: edg-transfer

Description: The edg-transfer function transfers tokens from sender to recipient with only DAO/extension authorization — it does not verify the sender consented to the transfer.

(define-public (edg-transfer (amount uint) (sender principal) (recipient principal))
  (begin
    (try! (is-dao-or-extension))
    (ft-transfer? alex amount sender recipient)
  )
)

Impact: Any DAO extension can unilaterally move tokens from one user to another. This is a powerful privilege that extends beyond normal token operations. Combined with the DAO's ability to add extensions dynamically, a malicious proposal could drain any holder's tokens.

Recommendation: By design for DAO governance, but this is one of the most sensitive functions in the contract. In a Clarity 4 migration, as-contract? with with-ft allowances would provide language-level constraints on which tokens can be moved.

M-03 — No mechanism to remove approved contracts

Location: edg-add-approved-contract, approved-contracts map

Description: The contract provides edg-add-approved-contract to add entries to the approved-contracts map (setting them to true), but there is no corresponding removal function. Once a contract is approved, it remains approved forever unless the governance token contract itself is replaced.

(define-public (edg-add-approved-contract (new-approved-contract principal))
  (begin
    (try! (is-dao-or-extension))
    (ok (map-set approved-contracts new-approved-contract true))
  )
)

Impact: If an approved contract is found to have a vulnerability, it cannot be deauthorized. Approved contracts can mint and burn tokens (including burning from arbitrary owners per M-01). The only mitigation would be to deploy a new governance token contract entirely.

Recommendation: Add a edg-remove-approved-contract function (or generalize to edg-set-approved-contract taking a bool). This would allow the DAO to revoke privileges without redeploying.

Low Findings

L-01 — Unlimited token supply with no cap

Location: define-fungible-token alex

Description: The alex token is defined without a max supply parameter: (define-fungible-token alex). This means there is no protocol-level cap on minting — supply is bounded only by the uint max value.

Impact: The DAO (or any approved contract) can mint unlimited tokens. While governance controls minting, there is no safety net against hyperinflation through a malicious proposal.

Recommendation: Consider defining a max supply: (define-fungible-token alex u1000000000000000). This provides a hard ceiling that even governance cannot override.

L-02 — Mutable decimals can break integrations

Location: set-decimals

Description: The set-decimals function allows the DAO to change the token's decimal count post-deployment. This also affects the fixed-point math layer since pow-decimals uses the mutable value.

(define-public (set-decimals (new-decimals uint))
  (begin
    (try! (is-dao-or-extension))
    (ok (var-set token-decimals new-decimals))
  )
)

Impact: Changing decimals after deployment would break all DEX integrations, wallet displays, and the fixed-point conversion functions (fixed-to-decimals, decimals-to-fixed). If decimals were changed to 0, pow-decimals would return 1 and the fixed-point layer would produce wildly incorrect values.

Recommendation: Make decimals immutable: (define-constant token-decimals u8). Mutable name/symbol/URI are acceptable (rebranding), but decimals should never change.

L-03 — edg-mint-many silently includes failed mints in result

Location: edg-mint-many, edg-mint-many-iter

Description: The helper edg-mint-many-iter calls ft-mint? directly (not wrapped in try!), and the outer function uses map which collects results. If any individual mint fails (e.g., to an invalid principal or due to supply cap), the error is silently included in the result list rather than aborting the whole batch.

(define-private (edg-mint-many-iter (item {amount: uint, recipient: principal}))
  (ft-mint? alex (get amount item) (get recipient item))
)

(define-public (edg-mint-many (recipients (list 200 {amount: uint, recipient: principal})))
  (begin
    (try! (is-dao-or-extension))
    (ok (map edg-mint-many-iter recipients))
  )
)

Impact: A batch mint that partially fails could go unnoticed by the calling proposal. The caller receives (ok (list ...)) containing a mix of (ok true) and (err ...) values with no aggregate failure signal.

Recommendation: Use fold with error propagation instead of map, or have the iterator use unwrap-panic to abort on any failure.

Informational Findings

I-01 — Pre-Clarity 4 contract

Location: Contract-wide

Description: This contract was deployed before Clarity 4 was available and does not use the new as-contract? safety builtins. While this contract doesn't use as-contract (it doesn't hold assets), a Clarity 4 migration would benefit the broader ExecutorDAO system that interacts with this token.

Recommendation: When upgrading the DAO system, consider migrating to Clarity 4 with explicit asset allowances on any contracts that call edg-transfer or edg-mint.

I-02 — Hardcoded approved contracts at deployment

Location: Contract tail

Description: Four contracts are hardcoded as approved at deploy time:

(map-set approved-contracts .alex-reserve-pool true)
(map-set approved-contracts .exchange true)
(map-set approved-contracts .faucet true)
(map-set approved-contracts tx-sender true)

Notably, tx-sender (the deployer) is given permanent mint/burn approval, and a .faucet contract is included.

Impact: The deployer retains mint/burn privileges independent of the DAO. If the deployer key is compromised, tokens can be minted freely. Combined with M-03 (no removal mechanism), this approval cannot be revoked.

I-03 — get-balance includes locked tokens

Location: get-balance

Description: The SIP-010 get-balance function returns the sum of both alex and alex-locked balances. This means the reported balance includes non-transferable locked tokens.

(define-read-only (get-balance (who principal))
  (ok (+ (ft-get-balance alex who) (ft-get-balance alex-locked who)))
)

Impact: Integrations relying on get-balance to determine transferable tokens will overestimate. The separate edg-get-locked function exists for locked balance queries, but standard SIP-010 consumers won't know to use it.

Positive Observations

Recommendations Summary

  1. Add approved-contract removal — Critical operational gap (M-03). A single edg-set-approved-contract function with a bool parameter would solve this.
  2. Make decimals immutable — Changing decimals post-deployment would catastrophically break integrations (L-02).
  3. Consider deployer privilege — The hardcoded tx-sender approval (I-02) means the deployer retains mint/burn power permanently. Evaluate whether this is still needed.
  4. Cap token supply — Even a generous cap provides a safety net against accidental hyperinflation (L-01).