Charismatic Flow Hold-to-Earn Engine

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

Overview

ContractSP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.charismatic-flow-hold-to-earn
DeployerSP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ
Block Height469,555
Clarity Version3 (pre-Clarity 4)
SourceHiro API (on-chain)
Lines of Code180
ConfidenceMedium — single-contract audit; external dependencies (engine-coordinator, charisma-rulebook-v0) not fully inspected

Description: A hold-to-earn mechanism for the Charismatic Flow LP token. It rewards long-term holders by computing the integral of their token balance over time using the trapezoidal rule (numerical integration with 2–39 sample points). Users call tap to claim "energy" proportional to their balance-over-time, scaled by an incentive score and inverse to total supply.

Documented Limitations

Findings Summary

0
Critical
1
High
2
Medium
1
Low
2
Info
IDSeverityTitle
H-01HighDiscrete Balance Sampling Allows Inter-Sample Manipulation
M-01MediumDivision-by-Zero Panic if Total Supply Reaches Zero
M-02MediumInteger Division Truncation Causes Precision Loss
L-01LowNo Minimum Tap Interval
I-01InfoPre-Clarity 4 Contract — No as-contract? Safety
I-02InfoHardcoded External Contract References

Detailed Findings

High H-01: Discrete Balance Sampling Allows Inter-Sample Manipulation

Location: get-balance, calculate-balance-integral-*

Description: The balance integral is computed by sampling the user's token balance at discrete block heights (2–39 points over the claim period) using the trapezoidal rule. The contract only observes balance at these specific sample blocks — it has no visibility into balances at intermediate blocks.

An attacker can exploit this by:

  1. Acquiring a large number of Charismatic Flow tokens shortly before a sample point block
  2. Having the inflated balance measured at the sample point
  3. Selling the tokens immediately after the sample block

With only 39 sample points spread over potentially thousands of blocks, the contract measures a high balance at each sample while the attacker only held tokens for a fraction of the period. The trapezoidal areas between samples assume linear interpolation, so a spike at a sample point inflates the area for two adjacent trapezoids.

;; Only 39 discrete samples over the entire claim period
(define-private (calculate-balance-integral-39 (address principal) (start-block uint) (end-block uint))
    (let (
        (sample-points (contract-call? 'SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.engine-coordinator
            generate-sample-points-39 address start-block end-block))
        (balances (map get-balance sample-points))
        ...))

Impact: An attacker who can predict sample block heights (they are deterministic based on start/end blocks) can amplify their energy rewards far beyond what their actual holding duration warrants. The economic damage scales with the incentive score and the attacker's ability to borrow tokens (e.g., via DEX flash-trades or coordinated timing).

Recommendation: Consider: (1) using a commit-reveal scheme where the sample blocks are unpredictable; (2) increasing the minimum sample density; (3) adding a minimum hold duration between token transfers and tap eligibility; or (4) using a time-weighted average balance tracked on every transfer event rather than retrospective sampling.

Medium M-01: Division-by-Zero Panic if Total Supply Reaches Zero

Location: tap function

Description: The tap function computes potential-energy by dividing by the total supply of Charismatic Flow tokens. If the total supply is ever zero (all tokens burned or not yet minted), this division panics and aborts the transaction.

(supply (unwrap-panic (contract-call? 'SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.charismatic-flow get-total-supply)))
(potential-energy (/ (* balance-integral incentive-score) supply))

Impact: If supply reaches zero, the tap function becomes permanently unusable until supply is restored. No funds are at risk, but the contract is bricked. In practice, supply reaching zero is unlikely for an active LP token, but the lack of a guard is a design weakness.

Recommendation: Add a zero-supply guard: (asserts! (> supply u0) (err u0)) to return a clean error instead of panicking.

Medium M-02: Integer Division Truncation Causes Precision Loss

Location: calculate-balance-integral-* functions

Description: The step size dx is computed as (/ (- end-block start-block) u38) (or u18, u8, u4, u1 for smaller variants). Clarity integer division truncates toward zero. For small block ranges, this truncation loses a significant fraction of the measured period.

Example: If the block difference is 40 and using 39-point sampling, dx = 40 / 38 = 1 (truncated from 1.05). The total measured span becomes 38 × 1 = 38 blocks, losing 2 blocks (5%). For a 39-block difference: dx = 39 / 38 = 1, total measured = 38, losing 1 block. The threshold system mitigates this by routing small ranges to fewer sample points, but edge cases near thresholds still have notable truncation.

(dx (/ (- end-block start-block) u38))
;; For block-difference = 45: dx = 1, total measured = 38, losing 7 blocks (15.6%)

Impact: Users consistently receive slightly less energy than they mathematically should. The truncation error favors the protocol (underpayment). Not exploitable, but systematically unfair to users, especially those tapping near threshold boundaries.

Recommendation: Scale up by a precision factor before dividing: e.g., compute dx * 1e6 and divide the final result by 1e6 to preserve precision. Alternatively, compute trapezoid areas as (b1 + b2) * (end - start) / (2 * N) directly without a separate dx step.

Low L-01: No Minimum Tap Interval

Location: tap function

Description: Users can call tap every single block. For very short intervals (1–2 blocks), the contract runs the full integration machinery (fetching historical balances via at-block, external contract calls) to produce negligible energy rewards. There is no minimum cooldown period.

Impact: Low. While this creates unnecessary chain load and costs the caller transaction fees, it doesn't produce excess rewards — the integral over a tiny period is proportionally small. The main concern is gas waste and unnecessary at-block lookups.

Recommendation: Add a minimum block difference check: (asserts! (>= (- end-block start-block) u10) (err u1)).

Info I-01: Pre-Clarity 4 Contract — No as-contract? Safety

Location: Contract-wide

Description: This contract is deployed with Clarity version 3. While the contract itself does not use as-contract, it calls into charisma-rulebook-v0.energize which likely does. Clarity 4 introduced as-contract? with explicit asset allowances (with-ft, with-nft, with-stx), providing language-level guarantees about which assets a contract can move.

Recommendation: When upgrading the hold-to-earn engine, deploy on Clarity 4 and leverage as-contract? in any downstream contracts for stricter asset safety.

Info I-02: Hardcoded External Contract References

Location: Contract-wide

Description: All external contract calls reference hardcoded principals (SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.charismatic-flow, .engine-coordinator, .charisma-rulebook-v0). This is standard for deployed Clarity contracts and provides safety (no dynamic dispatch), but means any upgrade to the referenced contracts requires redeploying the hold-to-earn engine.

Recommendation: Informational only. This is the expected pattern for Clarity contracts on Stacks.

Architecture Notes

Trapezoidal Integration Design

The contract implements a creative approach to hold-to-earn: rather than tracking deposits/withdrawals in real-time, it retrospectively computes the area under the balance curve using the trapezoidal rule. This is mathematically sound and avoids the gas cost of per-transfer bookkeeping, but the discrete sampling introduces the vulnerability described in H-01.

Adaptive Sample Density

The contract adapts its sample count (2, 5, 9, 19, or 39 points) based on the block range, using thresholds from engine-coordinator. This is a good design — it avoids wasting gas on too many at-block lookups for short periods while providing reasonable accuracy for long periods.

Checks-Effects-Interactions Pattern

The contract correctly updates last-tap-block (effect) before calling the external energize function (interaction), following the checks-effects-interactions pattern to prevent reentrancy.

Positive Observations