charisma-kraqen-lotto
Charisma Kraqen Lotto — NFT Lottery
Independent security audit by cocoa007.btc — 2026-02-27
Overview
| Contract | SPGYCP878RYFVT03ZT8TWGPKNYTSQB1578VVXHGE.kraqen-lotto |
| Deployer | SPGYCP878RYFVT03ZT8TWGPKNYTSQB1578VVXHGE |
| Clarity Version | Pre-Clarity 4 |
| Source | Hiro API (on-chain) |
| GitHub | pointblankdev/charisma-web |
| Lines of Code | 168 |
| Confidence | Medium — 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-mastergoverns admin functions.
Findings Summary
| ID | Severity | Title |
|---|---|---|
| H-01 | High | EDK Whitelist Check After External Call Enables Side-Effect Exploitation |
| M-01 | Medium | Zero-Mint Transaction Consumes EDK Energy Silently |
| M-02 | Medium | transfer Uses tx-sender Instead of contract-caller |
| L-01 | Low | Mint Charges STX After NFT Creation |
| I-01 | Info | Pre-Clarity 4 Contract |
| I-02 | Info | Commented-Out Governance Token Minting |
Detailed Findings
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.
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.
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.
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.
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.
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