Charisma Token (CHA)

SIP-010 fungible token wrapper with liquidity locks, red-pill gates, and DAO governance โ€” core token of the Charisma gamified DeFi protocol

ContractSP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.charisma-token
ProtocolCharisma โ€” gamified DeFi on Stacks
SourceVerified on-chain via Hiro API (/v2/contracts/source)
Clarity VersionPre-Clarity 4 (uses as-contract without asset allowances)
Lines of Code226
SHA-2564cbb8c40d54f67887194c4b63987ffbb4645d0474df60ef5795f89d03fa2635c
Confidence๐ŸŸข HIGH โ€” self-contained wrapper contract, all logic auditable inline; external dependencies (dungeon-master, red-pill-nft, dme000-governance-token) noted but not reviewed
DateFebruary 24, 2026
0
Critical
1
High
2
Medium
2
Low
3
Informational

Overview

CHA is a wrapped token representing the Charisma governance token (dme000-governance-token). Users wrap governance tokens into CHA and unwrap CHA back โ€” a 1:1 wrapper with rate-limiting mechanics:

Architecture

Trust Assumptions

Findings

HIGH

H-01: Liquidity flow cap on unwrap is dead code โ€” always bypassed

Location: unwrap function, lines ~113-126

Description: The unwrap function attempts to cap the withdrawal amount using max-liquidity-flow, but the cap condition can never trigger. The guard (try! (is-red-pilled)) aborts the transaction entirely if the caller lacks the red-pill NFT, meaning execution only continues when red-pilled is (ok true) โ€” i.e., the bound variable red-pilled is always true.

The cap condition is:

(amount-out (if (and (not red-pilled) (> amount max-amount)) max-amount amount))

Since red-pilled is always true, (not red-pilled) is always false, the and short-circuits, and amount-out is always amount โ€” the cap never applies.

Impact: Any red-pill holder can unwrap an unlimited amount of CHA in a single transaction, bypassing the intended liquidity flow rate limit. The wrap function correctly caps at max-liquidity-flow, but unwrap does not. This creates an asymmetric rate limit: inflows are throttled but outflows are not. A large holder could drain all governance tokens held by the contract in a single unwrap, potentially impacting other users who haven't unwrapped yet.

Recommendation: Remove the (not red-pilled) condition and apply the cap unconditionally, matching the wrap logic:

;; Fix: match the wrap function's capping logic
(amount-out (if (> amount max-amount) max-amount amount))
MEDIUM

M-01: Monotonically increasing block-counter will eventually permanently lock all wrapping/unwrapping

Location: wrap and unwrap functions; is-unlocked read-only

Description: Every call to wrap or unwrap increments block-counter by blocks-per-tx (default 1). The unlock check requires:

(>= block-height (+ unlock-block (var-get block-counter)))

Since unlock-block is a constant set at deploy time and block-counter only ever increases, the required block height grows without bound. After enough transactions, the counter will push the unlock threshold far beyond the current block height, creating extended lockout periods. With blocks-per-tx = 1, after N transactions, the contract requires unlock-block + N blocks to have passed โ€” meaning the contract is locked until the chain catches up.

Impact: The contract becomes progressively harder to use over time. In high-activity periods, the lock could extend days or weeks into the future. The DAO can mitigate by reducing blocks-per-tx (minimum 1), but it cannot reset the counter โ€” there is no set-block-counter function. This is likely by design (rate limiting), but the lack of a reset mechanism or decay function means the lockout is permanent and cumulative.

Recommendation: Add a DAO-callable function to reset or reduce block-counter, or implement a sliding window mechanism instead of a cumulative counter. Alternatively, change the unlock logic to compare the last transaction block rather than accumulating a counter:

;; Alternative: per-transaction cooldown instead of cumulative counter
(define-data-var last-tx-block uint u0)
(define-read-only (is-unlocked)
  (ok (asserts! (>= block-height (+ (var-get last-tx-block) (var-get blocks-per-tx))) err-liquidity-lock))
)
MEDIUM

M-02: set-decimals allows post-deployment decimal change breaking all integrations

Location: set-decimals function

Description: The DAO can change the token's reported decimal places at any time. All existing balances, DeFi pool ratios, price feeds, and UI displays assume a fixed decimal value. Changing decimals from 6 to (say) 18 would make 1 CHA appear as 0.000000000001 CHA in all integrations, effectively repricing the token.

Impact: Breaking change for all downstream integrations โ€” DEX pools, wallets, block explorers, price oracles. Could be used maliciously by a compromised DAO to manipulate displayed prices.

Recommendation: Remove set-decimals and make decimals a constant, or add a governance timelock. Token decimals are a fundamental property that should not change post-deployment.

LOW

L-01: Pre-Clarity 4 as-contract grants blanket asset access

Location: unwrap function โ€” (as-contract (contract-call? ... transfer ...))

Description: The contract uses as-contract to transfer governance tokens from itself to the user during unwrap. In pre-Clarity 4, as-contract gives the enclosed expression access to move any asset the contract holds โ€” not just the intended governance tokens. If the contract ever holds other tokens (e.g., from accidental sends or future extensions), those could theoretically be extracted by a malicious extension that passes the is-dao-or-extension check.

Impact: Low โ€” the contract is purpose-built as a wrapper and shouldn't hold arbitrary assets. However, upgrading to Clarity 4's as-contract? with with-ft would provide defense in depth.

Recommendation: When upgrading to Clarity 4, use:

(as-contract? (with-ft 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dme000-governance-token)
  (contract-call? 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dme000-governance-token
    transfer amount-out current-contract sender none))
LOW

L-02: No input validation on wrap/unwrap amount โ€” zero-amount calls waste block-counter

Location: wrap and unwrap functions

Description: Neither wrap nor unwrap checks that the amount is greater than zero. A call with amount = 0 would still increment block-counter, consuming rate-limit budget without any economic purpose. Since ft-mint? and ft-burn? with zero amounts succeed in Clarity, the transaction completes normally.

Impact: A griefer with a red-pill NFT could repeatedly call wrap/unwrap with 0 to inflate the block-counter, extending the lockout for all users at minimal cost (only tx fees).

Recommendation: Add (asserts! (> amount u0) err-invalid-input) at the start of both functions.

INFO

I-01: No max supply defined for CHA token

Location: (define-fungible-token charisma)

Description: The token is defined without a maximum supply parameter. Supply is bounded only by the amount of governance tokens deposited (1:1 wrapping), so this is not exploitable โ€” but it means Clarity's built-in supply cap enforcement is not used.

Recommendation: Informational only. If a hard cap is desired, use (define-fungible-token charisma u<max-supply>).

INFO

I-02: transfer does not emit memo via print

Location: transfer function

Description: The SIP-010 transfer function accepts an optional memo but does not print it. While not required by SIP-010, many indexers and explorers rely on printed memos for transfer tracking and metadata.

Recommendation: Add a conditional print:

(match memo to-print (print to-print) 0x)
INFO

I-03: unlock-block captures deploy-time block height

Location: (define-constant unlock-block block-height)

Description: The unlock block is permanently set to the block height at deployment. Combined with the cumulative block-counter, this means the "unlock" threshold is always deploy-block + total-tx-count. This is the intended design for rate-limiting but means the contract's behavior depends on when it was deployed โ€” redeploying at a later block height would effectively reset the rate limit.

Recommendation: Informational. Document this behavior for integrators.

Positive Observations

Summary

The Charisma Token is a well-structured SIP-010 wrapper with thoughtful rate-limiting mechanics. The primary concern is H-01: the liquidity flow cap on unwrap is completely bypassed due to a logic error in the red-pill check, creating an asymmetry where inflows are throttled but outflows are not. The cumulative block-counter design (M-01) will eventually create permanent lockouts without DAO intervention, and the mutable decimals (M-02) are a footgun for integrators. Overall, the contract's attack surface is limited by its single-purpose design โ€” it holds governance tokens and wraps/unwraps them, with no complex DeFi logic.