Modular rewards system for the Charisma DAO — mints governance tokens (CHA) as quest completion rewards
| Contract | SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.dme009-charisma-rewards |
| Protocol | Charisma — gamified DeFi on Stacks |
| Source | Verified on-chain via Hiro API + boomcrypto mirror |
| Clarity Version | 2 (pre-Clarity 4) |
| Lines of Code | ~65 |
| Deploy Block | 117,449 |
| Confidence | 🟢 HIGH — self-contained, minimal logic; external dependency on dungeon-master DAO noted but not reviewed |
| Date | 2026-02-25 |
DME009 Charisma Rewards is a lean DAO extension contract in the Charisma protocol. It serves a single purpose: mapping quest IDs to reward amounts and minting Charisma governance tokens (via dme000-governance-token.dmg-mint) when quests are completed.
is-dao-or-extension, which checks if the caller is the dungeon-master DAO contract or an approved extension.quest-rewards-map (quest-id → reward amount) and quest-locked-map (address+quest-id → locked boolean).claim(quest-id) looks up the reward amount and calls dmg-mint to mint tokens to tx-sender (the calling extension).dungeon-master DAO contract and which extensions it authorizes.dme000-governance-token must honor mint requests from this contract.Location: set-rewards function
Description: The set-rewards function accepts any uint value with no upper bound check. A compromised or malicious DAO extension could set an astronomically large reward for a quest, enabling unbounded governance token minting.
(define-public (set-rewards (quest-id uint) (amount uint))
(begin
(try! (is-dao-or-extension))
(ok (map-set quest-rewards-map quest-id amount))
)
)
Impact: If the DAO authorization layer is compromised, this becomes an unlimited mint vector for governance tokens, diluting all existing holders.
Recommendation: Add a max-reward-per-quest constant (e.g., u1000000000000) and assert the amount is below it. This provides defense-in-depth even if DAO governance is compromised.
(define-constant max-reward-per-quest u1000000000000) ;; 1M CHA with 6 decimals
(define-public (set-rewards (quest-id uint) (amount uint))
(begin
(try! (is-dao-or-extension))
(asserts! (<= amount max-reward-per-quest) (err u2002))
(ok (map-set quest-rewards-map quest-id amount))
)
)
Description: The contract is deployed with Clarity version 2. While it does not use as-contract (so no immediate risk), future upgrades of this extension pattern should use Clarity 4's as-contract? with explicit asset allowances for safer token operations.
Recommendation: When deploying successor versions, use Clarity 4 and as-contract? with with-ft allowances to explicitly limit which tokens the contract can move.
Description: The claim function mints tokens to tx-sender. Since claim is gated by is-dao-or-extension, only DAO extensions can call it. This means tokens are minted to the extension contract, not the end user. The calling extension is responsible for distributing tokens to the actual user.
(define-public (claim (quest-id uint))
(begin
(try! (is-dao-or-extension))
(let ((reward-amount (default-to u0 (map-get? quest-rewards-map quest-id))))
(contract-call? 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dme000-governance-token
dmg-mint reward-amount tx-sender)
)
)
)
Impact: No bug — this is by design. The extension pattern delegates user-facing logic to the calling contract. However, any extension calling claim must forward tokens to the user or they accumulate in the extension.
Description: The contract does not emit any print events when rewards are claimed or configured. Off-chain indexers must parse transaction receipts and contract-call? results to track reward activity.
Recommendation: Add (print { event: "claim", quest-id: quest-id, amount: reward-amount, recipient: tx-sender }) in the claim function for better observability.
is-dao-or-extension before executing. No unprotected state mutations.default-to u0 for missing quest rewards means unknown quests yield zero tokens, not a runtime error.extension-trait with the required callback function.quest-locked-map provides a per-user, per-quest lock that prevents double claims (enforced by the calling extension).;; Title: DME009 Charisma Rewards
;; Author: rozar.btc
;; Depends-On: DME000, DME001
;; Synopsis:
;; A modular rewards system that disburses Charisma rewards for quest completions.
(impl-trait 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.extension-trait.extension-trait)
(define-constant err-not-found (err u2001))
(define-constant err-unauthorized (err u3100))
(define-map quest-rewards-map uint uint)
(define-map quest-locked-map
{ address: principal, quest-id: uint }
bool
)
;; --- Authorization check
(define-public (is-dao-or-extension)
(ok (asserts! (or (is-eq tx-sender
'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master)
(contract-call?
'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master
is-extension contract-caller))
err-unauthorized))
)
;; --- Internal DAO functions
(define-public (claim (quest-id uint))
(begin
(try! (is-dao-or-extension))
(let ((reward-amount (default-to u0 (map-get? quest-rewards-map quest-id))))
(contract-call?
'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dme000-governance-token
dmg-mint reward-amount tx-sender)
)
)
)
(define-public (set-rewards (quest-id uint) (amount uint))
(begin
(try! (is-dao-or-extension))
(ok (map-set quest-rewards-map quest-id amount))
)
)
(define-public (set-locked (address principal) (quest-id uint) (locked bool))
(begin
(try! (is-dao-or-extension))
(ok (map-set quest-locked-map { address: address, quest-id: quest-id } locked))
)
)
;; --- Public functions
(define-read-only (get-rewards (quest-id uint))
(ok (default-to u0 (map-get? quest-rewards-map quest-id)))
)
(define-read-only (is-locked (address principal) (quest-id uint))
(ok (default-to false (map-get? quest-locked-map
{ address: address, quest-id: quest-id })))
)
;; --- Extension callback
(define-public (callback (sender principal) (memo (buff 34)))
(ok true)
)