DME009 Charisma Rewards

Modular rewards system for the Charisma DAO — mints governance tokens (CHA) as quest completion rewards

ContractSP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.dme009-charisma-rewards
ProtocolCharisma — gamified DeFi on Stacks
SourceVerified on-chain via Hiro API + boomcrypto mirror
Clarity Version2 (pre-Clarity 4)
Lines of Code~65
Deploy Block117,449
Confidence🟢 HIGH — self-contained, minimal logic; external dependency on dungeon-master DAO noted but not reviewed
Date2026-02-25
0
Critical
0
High
0
Medium
1
Low
3
Informational

Overview

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.

Architecture

Trust Assumptions

Findings

LOW

L-01: No Maximum Reward Cap

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))
  )
)
INFO

I-01: Clarity Version 2 — Pre-Clarity 4

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.

INFO

I-02: tx-sender Recipient in claim() is the Calling Extension

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.

INFO

I-03: No Event Emission for Claims

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.

Positive Observations

Full Source

;; 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)
)