Charisma protocol's primary user-facing router — batches up to 8 interaction contract calls into a single transaction with best-effort error handling
| Contract | SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.dungeon-crawler-rc6 |
| Protocol | Charisma — gamified DeFi on Stacks |
| Source | Verified on-chain via Hiro API (/v2/contracts/source) |
| Clarity Version | Clarity 2 (pre-Clarity 4 — no as-contract? asset allowances) |
| Block Height | 170,992 |
| Lines of Code | 74 |
| Audit Date | 2026-02-25 |
| Confidence | 🟢 HIGH — simple contract, fully reviewed; security depends on downstream interaction contracts (not in scope) |
The Dungeon Crawler RC6 is the primary user-facing entry point for the Charisma protocol. It's a lightweight router that dispatches calls to "interaction" contracts — any contract implementing interaction-trait from .dao-traits-v7. The trait requires a single function: (execute (action (string-ascii 32))).
The contract provides two public functions:
interact — executes a single interaction, propagating errors to the callerexplore — batches up to 8 interactions in one transaction with best-effort execution (errors are swallowed per-interaction)The contract holds no state, no funds, and has no access controls. All security is delegated to the interaction contracts it calls, which are verified through the Charisma Rulebook's allowlist system. This is a deliberate architectural choice — the Dungeon Crawler is a thin, non-privileged dispatcher.
The Charisma protocol uses a layered security model:
The Dungeon Crawler's explore function enables "multi-action turns" where a user can farm, swap, stake, and claim in a single transaction.
explore Panics on Partial Input Despite Optional ParametersLocation: explore function, output tuple construction (lines 63-72)
Description: The explore function accepts 8 optional interaction/action pairs, suggesting callers can provide fewer than 8. The execution logic correctly checks (is-some action-N) before calling interact. However, the output tuple at the end unconditionally calls (unwrap-panic interaction-N) and (unwrap-panic action-N) for all 8 slots, regardless of whether they were provided.
If a caller provides none for any interaction or action slot, the entire transaction aborts with a runtime panic — even if the interaction execution itself was safely skipped.
;; Execution correctly guards with is-some:
(response-1 (if (is-some action-1) (match (interact ...) ...) ""))
;; But output ALWAYS unwraps — panics if none:
(output {
i1: {i: (unwrap-panic interaction-1), ;; <-- panics if none
a: (unwrap-panic action-1), ;; <-- panics if none
r: response-1},
;; ... same for all 8 slots
})
Impact: The explore function is unusable with fewer than 8 interactions. Callers MUST provide all 8 interaction/action pairs, even if they only want to execute 1-7 actions. The optional type signature is misleading and creates a false sense of flexibility. Users who attempt to use it as designed (with some none slots) will have their transactions revert, wasting fees.
Recommendation: Replace the unconditional unwrap-panic in the output with conditional handling:
;; Option A: Use default values for none slots
i1: {i: (default-to 'SP000000000000000000002Q6VF78.none interaction-1),
a: (default-to "" action-1),
r: response-1}
;; Option B: Return a simpler output with just response strings
(ok {r1: response-1, r2: response-2, ... r8: response-8})
Location: explore function, response bindings (lines 56-63)
Description: The explore function uses match to convert both success and error responses into the same type ((string-ascii ...)). Both branches bind to a variable and return it as-is, making it impossible to distinguish a successful interaction from a failed one in the output.
(response-1 (if (is-some action-1)
(match (interact (unwrap-panic interaction-1) (unwrap-panic action-1))
success success ;; success response string
error error) ;; error response string — same type!
""))
Impact: Callers and off-chain indexers cannot reliably determine which interactions succeeded and which failed. A user might believe all 8 actions completed when some silently failed. This is especially problematic for DeFi operations where partial execution could leave a user in an unintended state (e.g., energy burned but reward not received).
Recommendation: Return structured results that distinguish success from failure:
;; Return optional — none means failure
(response-1 (if (is-some action-1)
(match (interact ...)
success (some success)
error none)
none))
Alternatively, the print statement at the end provides some observability, but this is only available to off-chain readers and requires parsing event logs.
Location: interact function (line 46)
Description: Anyone can call interact or explore with any contract that implements interaction-trait. The Dungeon Crawler performs no verification of whether the interaction contract is authorized by the protocol.
Impact: If a malicious contract implements interaction-trait, it can be called through the Dungeon Crawler. However, the actual risk is minimal because:
contract-caller = Dungeon Crawler, not the user's principal, so it cannot impersonate the user for privileged operationsThis is an architectural observation rather than a vulnerability — the security model deliberately places authorization at the Rulebook layer.
Recommendation: Consider adding an optional allowlist at the Dungeon Crawler level for defense-in-depth. This would catch unauthorized interactions before they reach downstream contracts, reducing gas waste on obviously invalid calls.
Description: This contract uses Clarity 2. While the Dungeon Crawler itself doesn't use as-contract, the broader Charisma ecosystem would benefit from Clarity 4's as-contract? with explicit asset allowances. A future version (rc7+) deployed with Clarity 4 would be able to use restrict-assets? to limit what interaction contracts can do when called through this router.
Description: The explore function emits a print with the full output tuple including all interaction addresses, actions, and responses. This provides good observability for off-chain indexers and is a positive pattern. The interact function does not emit its own print (relying on the interaction contract to do so).
The Dungeon Crawler RC6 is a minimal, well-scoped router contract. Its primary vulnerability is the broken optional parameter handling in explore (H-01), which forces callers to always provide all 8 interaction slots. Combined with silent error swallowing (M-01), this creates a poor developer experience but does not directly endanger user funds.
The contract's security model is sound in principle — it deliberately avoids holding state or privileges, delegating all authorization to the Rulebook layer. The main risk is at the ecosystem level: if the Rulebook or interaction contracts have vulnerabilities, the Dungeon Crawler will faithfully route exploits through. This is out of scope for this audit but noted for completeness.
Given RC6 in the contract name, this appears to be an iterative release. The explore output bug (H-01) should be fixed in RC7.