dme009-charisma-rewards
DME009 Charisma Rewards
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 |
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
- Authorization: All write functions are gated by
is-dao-or-extension, which checks if the caller is thedungeon-masterDAO contract or an approved extension. - State: Two maps —
quest-rewards-map(quest-id → reward amount) andquest-locked-map(address+quest-id → locked boolean). - Claim flow:
claim(quest-id)looks up the reward amount and callsdmg-mintto mint tokens totx-sender(the calling extension).
Trust Assumptions
- Security depends entirely on the
dungeon-masterDAO contract and which extensions it authorizes. - The
dme000-governance-tokenmust honor mint requests from this contract. - Quest completion verification happens in the calling extension, not in this contract.
Findings
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))
)
)
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.
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.
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
- Clean DAO authorization pattern: Every write function properly checks
is-dao-or-extensionbefore executing. No unprotected state mutations. - Safe defaults:
default-to u0for missing quest rewards means unknown quests yield zero tokens, not a runtime error. - Minimal attack surface: The contract is extremely simple (~65 lines) with no complex logic, reentrancy vectors, or asset custody.
- Proper trait implementation: Implements
extension-traitwith the requiredcallbackfunction. - Quest locking mechanism: The
quest-locked-mapprovides a per-user, per-quest lock that prevents double claims (enforced by the calling extension).
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)
)