charisma-kraqen-lotto

2026-01-01

Charisma Kraqen Lotto — NFT Lottery

Independent security audit by cocoa007.btc — 2026-02-27

Overview

ContractSPGYCP878RYFVT03ZT8TWGPKNYTSQB1578VVXHGE.kraqen-lotto
DeployerSPGYCP878RYFVT03ZT8TWGPKNYTSQB1578VVXHGE
Clarity VersionPre-Clarity 4
SourceHiro API (on-chain)
GitHubpointblankdev/charisma-web
Lines of Code168
ConfidenceMedium — single-contract audit; EDK external dependencies not fully inspected

Description: A SIP-009 NFT lottery for the Charisma ecosystem. Users call tap with an Energy Distribution Kit (EDK) contract to earn energy and automatically mint 1–4 NFTs per transaction. Each mint costs 1 STX sent to dungeon-master. The collection is limited to 1,000 NFTs. Part of the Charisma DAO "quest" system where on-chain activities produce energy that converts to collectibles.

Documented Limitations

  • Governance token minting to the OWNER (5 CHA per mint) is commented out in the deployed code.
  • Relies on external whitelisted EDK contracts for energy calculation — energy values are trusted from EDKs.
  • Part of the Charisma DAO ecosystem; dungeon-master governs admin functions.

Findings Summary

0
Critical
1
High
2
Medium
1
Low
2
Info
IDSeverityTitle
H-01HighEDK Whitelist Check After External Call Enables Side-Effect Exploitation
M-01MediumZero-Mint Transaction Consumes EDK Energy Silently
M-02Mediumtransfer Uses tx-sender Instead of contract-caller
L-01LowMint Charges STX After NFT Creation
I-01InfoPre-Clarity 4 Contract
I-02InfoCommented-Out Governance Token Minting

Detailed Findings

High H-01: EDK Whitelist Check After External Call Enables Side-Effect Exploitation

Location: tap function

Description: The tap function calls the EDK contract's tap method before verifying the EDK is whitelisted. While the whitelist check will ultimately reject non-whitelisted EDKs, the external call has already executed by that point. More critically, for whitelisted-but-compromised EDKs, the returned energy value directly controls how many NFTs are minted (and how much STX the user pays), with no upper bound validation beyond the 4-NFT-per-tx cap.

(define-public (tap (land-id uint) (edk-contract <edk-trait>))
    (let
        (
            ;; External call happens FIRST
            (tapped-out (unwrap-panic (contract-call? edk-contract tap land-id)))
            (energy (get energy tapped-out))
            (nfts-to-mint (min (/ energy ENERGY_PER_NFT) MAX_NFTS_PER_TX))
        )
        ;; Whitelist check happens AFTER
        (asserts! (is-whitelisted-edk (contract-of edk-contract)) ERR_INVALID_EDK)
        (mint-multiple tx-sender nfts-to-mint)
    )
)

Impact: A non-whitelisted EDK can execute arbitrary side effects in its tap function before being rejected. For whitelisted EDKs, a compromised contract could return inflated energy values, causing users to unexpectedly pay up to 4 STX per transaction. The EDK's tap function may also consume user resources (energy, tokens) in the EDK's own state before the result is used here.

Recommendation: Move the whitelist assertion before the external call:

(asserts! (is-whitelisted-edk (contract-of edk-contract)) ERR_INVALID_EDK)
(let ((tapped-out (unwrap-panic (contract-call? edk-contract tap land-id))) ...)

Also consider allowing users to specify a max-nfts parameter to cap their spend.

Medium M-01: Zero-Mint Transaction Consumes EDK Energy Silently

Location: tap function, mint-multiple

Description: If the EDK returns energy less than 1,000 (the ENERGY_PER_NFT threshold), nfts-to-mint evaluates to 0. The mint-multiple function with count 0 falls through all if branches and returns (err u500). However, the EDK's tap function was already called and may have consumed the user's energy or resources in the EDK contract's state — the user loses resources but receives no NFT.

(nfts-to-mint (min (/ energy ENERGY_PER_NFT) MAX_NFTS_PER_TX))
;; If energy < 1000: nfts-to-mint = 0 → mint-multiple returns (err u500)
;; But EDK.tap() already executed and consumed user's energy

Impact: Users with insufficient energy lose their accumulated energy in the EDK without receiving any NFTs. The (err u500) return does abort the STX transfers and NFT mints, but the EDK's internal state change from tap already occurred within that call's scope. If the EDK's tap is idempotent or revertible on error, impact is lower.

Recommendation: Add a minimum energy assertion: (asserts! (>= nfts-to-mint u1) (err u501)) — but this doesn't prevent the EDK call. Ideally, restructure to check energy requirements before calling the EDK, or document this as expected behavior.

Medium M-02: transfer Uses tx-sender Instead of contract-caller

Location: transfer function

Description: The SIP-009 transfer function authenticates using tx-sender rather than contract-caller. This is a known anti-pattern in Clarity NFT contracts. When an intermediary contract calls transfer, tx-sender remains the original transaction signer, which could allow unintended transfers through contract composition attacks.

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    ;; #[filter(sender)]
    (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER)
    (nft-transfer? kraqen-lotto token-id sender recipient)
  )
)

Impact: A malicious contract could trick a user into signing a transaction that routes through the attacker's contract, which then calls transfer with the user as sender. The #[filter(sender)] annotation provides post-condition protection at the transaction level, mitigating exploitability. Risk is moderate given the annotation.

Recommendation: Use (or (is-eq tx-sender sender) (is-eq contract-caller sender)) for robust auth, or switch entirely to contract-caller.

Low L-01: Mint Charges STX After NFT Creation

Location: mint function

Description: The mint function creates the NFT via nft-mint? before transferring STX via stx-transfer?. While Clarity's atomic transactions ensure both succeed or both revert, the ordering doesn't follow the checks-effects-interactions pattern. Users without sufficient STX will have their transaction revert after compute is spent.

(try! (nft-mint? kraqen-lotto token-id recipient))
(try! (stx-transfer? STX_PER_MINT tx-sender .dungeon-master))

Impact: Low — transactions are atomic in Clarity. No funds at risk. Minor gas waste on reverted transactions.

Recommendation: Swap ordering to transfer STX first, or add a balance check upfront.

Info I-01: Pre-Clarity 4 Contract

Location: Contract-wide

Description: This contract predates Clarity 4. While it doesn't use as-contract, future versions or related contracts in the Charisma ecosystem should leverage Clarity 4's as-contract? with explicit asset allowances (with-stx, with-nft) for stricter safety guarantees.

Info I-02: Commented-Out Governance Token Minting

Location: mint function

Description: The line (try! (contract-call? .dme000-governance-token dmg-mint CHA_AMOUNT OWNER)) is commented out. This was designed to mint 5 CHA governance tokens to the collection creator per NFT mint. Its absence means the OWNER receives no CHA, which may be intentional (perhaps the governance token contract wasn't ready at deployment) or an oversight.

;; Mint 1 governance token to the OWNER
;; (try! (contract-call? .dme000-governance-token dmg-mint CHA_AMOUNT OWNER))

Impact: Informational. The OWNER misses out on CHA token rewards. Users still pay 1 STX to dungeon-master.

Architecture Notes

Quest-Based NFT Minting

The contract uses Charisma's "quest" pattern: users interact with EDK contracts that generate energy, which is then converted into NFT mints. This creates a gamification layer where on-chain activities (land interactions) produce collectibles. The pattern is elegant but introduces trust dependencies on whitelisted EDK contracts.

Hardcoded Mint-Multiple Pattern

Rather than using a loop (which Clarity doesn't natively support), the contract uses a cascading if tree in mint-multiple to handle 1–4 mints. This is a common Clarity pattern that's gas-efficient and avoids recursion complexity, though it's verbose and error-prone to maintain.

DAO Integration

Admin functions (set-whitelisted-edk, set-token-uri) require DAO authorization via dungeon-master. This is the standard Charisma/ExecutorDAO pattern — clean separation between governance and contract logic.

Positive Observations

  • Collection cap: Hard limit of 1,000 NFTs prevents unbounded minting
  • Per-tx cap: Maximum 4 NFTs per transaction limits per-call exposure
  • DAO-governed whitelist: EDK contracts must be explicitly whitelisted by the DAO
  • Clean SIP-009 implementation: Standard NFT interface with metadata URI support
  • Simple economics: Fixed 1 STX per mint — predictable cost structure

Independent audit by cocoa007.btc · Full audit portfolio

This audit is provided as-is with no guarantees. It represents a best-effort review at a point in time and should not be considered a comprehensive security certification.