SIP-010 fungible token wrapper with liquidity locks, red-pill gates, and DAO governance โ core token of the Charisma gamified DeFi protocol
| Contract | SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.charisma-token |
| Protocol | Charisma โ gamified DeFi on Stacks |
| Source | Verified on-chain via Hiro API (/v2/contracts/source) |
| Clarity Version | Pre-Clarity 4 (uses as-contract without asset allowances) |
| Lines of Code | 226 |
| SHA-256 | 4cbb8c40d54f67887194c4b63987ffbb4645d0474df60ef5795f89d03fa2635c |
| Confidence | ๐ข HIGH โ self-contained wrapper contract, all logic auditable inline; external dependencies (dungeon-master, red-pill-nft, dme000-governance-token) noted but not reviewed |
| Date | February 24, 2026 |
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:
red-pill-nft can wrap or unwrap.block-counter increments on each wrap/unwrap, creating a cooldown period before the next operation.max-liquidity-flow (default 100 tokens).dungeon-master DAO or its extensions.send-many for up to 200 recipients in a single transactiondungeon-master DAO (SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ) is trusted to configure parameters responsiblyis-extension) have full administrative power over token metadata and rate limitsred-pill-nft contract correctly reports holder statusunwrap is dead code โ always bypassedLocation: 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))
block-counter will eventually permanently lock all wrapping/unwrappingLocation: 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))
)
set-decimals allows post-deployment decimal change breaking all integrationsLocation: 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.
as-contract grants blanket asset accessLocation: 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))
wrap/unwrap amount โ zero-amount calls waste block-counterLocation: 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.
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>).
transfer does not emit memo via printLocation: 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)
unlock-block captures deploy-time block heightLocation: (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.
transferblocks-per-tx and max-liquidity-flow have min/max bounds, preventing extreme valuesset-token-uri properly emits a token-metadata-update event for indexersThe 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.