Source Code

;; Charisma Energy Generator
;;
;; Purpose:
;; This contract implements the core energy generation mechanism of the Charisma protocol.
;; It calculates energy output based on users' token balance history, utilizing integral
;; calculus for accurate and fair representation of token holding over time. The contract
;; incorporates dynamic multipliers to adjust energy generation based on token-specific
;; factors and protocol incentives.
;;
;; Key Features:
;; 1. Balance Integral Calculation: Computes the time-weighted average of token holdings
;;    using the trapezoidal rule approximation of integral calculus.
;; 2. Dynamic Multipliers: Applies quality scores and incentive scores to adjust energy output.
;; 3. Supply Normalization: Accounts for differences in token supplies across various assets.
;; 4. Retroactive Calculation: Allows for fair energy computation without active staking.
;; 5. Security-Enhanced: Users retain full control of their tokens while participating.
;;
;; This contract interacts with:
;; - SIP-010 token contracts for balance checking
;; - Arcana contract for quality scores and circulating supply information
;; - Aura contract for incentive scores
;;
;; The energy generated through this contract can be utilized in various aspects of the
;; Charisma ecosystem, including governance, rewards, GameFi mechanics, and other
;; decentralized applications built on top of the Charisma protocol.

(use-trait sip10-trait .dao-traits-v4.sip010-ft-trait)

;; Constants
(define-constant ERR_UNAUTHORIZED (err u401))
(define-constant ERR_INVALID_CLIENT (err u403))

;; Maps
(define-map last-tap-block principal uint)
(define-map enabled-clients principal bool)

;; Authorization controls

;; Check if the caller is the DAO or an authorized extension
(define-private (is-dao-or-extension)
    (or (is-eq tx-sender 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master) (contract-call? 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master is-extension contract-caller))
)

;; Enable or disable a client contract
(define-public (set-enabled-client (client-contract principal) (enabled bool))
  (begin
    (asserts! (is-dao-or-extension) ERR_UNAUTHORIZED)
    (ok (map-set enabled-clients client-contract enabled))
  )
)

;; Check if a client contract is enabled
(define-read-only (is-enabled-client (client-contract principal))
  (default-to false (map-get? enabled-clients client-contract))
)

;; Hold-to-Earn functions

;; Get the balance of an address at a specific block
(define-read-only (get-balance (data { address: principal, block: uint }))
    (let
        (
            (block-hash (unwrap-panic (get-block-info? id-header-hash (get block data))))
        )
        (at-block block-hash (unwrap-panic (contract-call? .charisma-token get-balance (get address data))))
    )
)

;; Generate sample points for balance integral calculation
(define-read-only (generate-sample-points (address principal) (start-block uint) (end-block uint))
    (let
        (
            (block-step (/ (- end-block start-block) u8))
        )
        (list
            { address: address, block: start-block }
            { address: address, block: (+ start-block block-step) }
            { address: address, block: (+ start-block (* block-step u2)) }
            { address: address, block: (+ start-block (* block-step u3)) }
            { address: address, block: (+ start-block (* block-step u4)) }
            { address: address, block: (+ start-block (* block-step u5)) }
            { address: address, block: (+ start-block (* block-step u6)) }
            { address: address, block: (+ start-block (* block-step u7)) }
            { address: address, block: end-block }
        )
    )
)

;; Calculate areas for the trapezoidal rule in balance integral
(define-read-only (calculate-trapezoid-areas (balances (list 9 uint)) (dx uint))
    (list
        (/ (* (+ (unwrap-panic (element-at balances u0)) (unwrap-panic (element-at balances u1))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u1)) (unwrap-panic (element-at balances u2))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u2)) (unwrap-panic (element-at balances u3))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u3)) (unwrap-panic (element-at balances u4))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u4)) (unwrap-panic (element-at balances u5))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u5)) (unwrap-panic (element-at balances u6))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u6)) (unwrap-panic (element-at balances u7))) dx) u2)
        (/ (* (+ (unwrap-panic (element-at balances u7)) (unwrap-panic (element-at balances u8))) dx) u2)
    )
)

;; Calculate the balance integral for a given address and block range
(define-read-only (calculate-balance-integral (address principal) (start-block uint) (end-block uint))
    (let
        (
            (sample-points (generate-sample-points address start-block end-block))
            (balances (map get-balance sample-points))
            (dx (/ (- end-block start-block) u8))
            (areas (calculate-trapezoid-areas balances dx))
        )
        (fold + areas u0)
    )
)

;; Get the last block where a user tapped for energy
(define-read-only (get-last-tap-block (address principal))
    ;; (default-to stacks-block-height (map-get? last-tap-block address))
    (default-to block-height (map-get? last-tap-block address))
)

;; Main function to calculate and claim energy
(define-public (tap)
  (let
    (
    ;;   (end-block stacks-block-height)
      (end-block block-height)
      (start-block (get-last-tap-block tx-sender))
      (balance-integral (calculate-balance-integral tx-sender start-block end-block))
      (quality-score (contract-call? .arcana get-quality-score .charisma-token))
      (incentive-score (contract-call? .aura get-incentive-score .charisma-token))
      (circulating-supply (contract-call? .arcana get-circulating-supply .charisma-token))
      (energy-output (/ (* (* balance-integral quality-score) incentive-score) circulating-supply))
    )
    (asserts! (is-enabled-client contract-caller) ERR_INVALID_CLIENT)
    (map-set last-tap-block tx-sender end-block)
    (ok energy-output)
  )
)

Functions (9)

FunctionAccessArgs
is-dao-or-extensionprivate
set-enabled-clientpublicclient-contract: principal, enabled: bool
is-enabled-clientread-onlyclient-contract: principal
get-balanceread-onlydata: { address: principal, block: uint }
generate-sample-pointsread-onlyaddress: principal, start-block: uint, end-block: uint
calculate-trapezoid-areasread-onlybalances: (list 9 uint
calculate-balance-integralread-onlyaddress: principal, start-block: uint, end-block: uint
get-last-tap-blockread-onlyaddress: principal
tappublic