Source Code

;; Vote Delegation - Delegated Voting Power (Clarity 4)
;; This contract allows users to delegate their voting power to others

;; Constants
(define-constant CONTRACT-OWNER tx-sender)

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u201))
(define-constant ERR-INVALID-DELEGATE (err u202))
(define-constant ERR-ALREADY-DELEGATED (err u203))
(define-constant ERR-NOT-DELEGATED (err u204))
(define-constant ERR-CIRCULAR-DELEGATION (err u205))
(define-constant ERR-SELF-DELEGATION (err u206))

;; Data maps

;; Delegation records with Clarity 4 timestamps
(define-map delegations
    { delegator: principal }
    {
        delegate: principal,
        delegated-at: uint,                  ;; Clarity 4: Unix timestamp
        expires-at: (optional uint),         ;; Clarity 4: Unix timestamp
        active: bool
    }
)

;; Track delegated power received
(define-map delegated-power
    { delegate: principal }
    {
        delegator-count: uint,
        total-power: uint,
        last-updated: uint                   ;; Clarity 4: Unix timestamp
    }
)

;; Delegation history with Clarity 4 timestamps
(define-map delegation-history
    { delegator: principal, index: uint }
    {
        delegate: principal,
        delegated-at: uint,                  ;; Clarity 4: Unix timestamp
        revoked-at: (optional uint)          ;; Clarity 4: Unix timestamp
    }
)

(define-map delegation-count { delegator: principal } uint)

;; Read-only functions

;; Get current delegation
(define-read-only (get-delegation (delegator principal))
    (ok (map-get? delegations { delegator: delegator }))
)

;; Get delegated power received by a delegate
(define-read-only (get-delegated-power (delegate principal))
    (ok (map-get? delegated-power { delegate: delegate }))
)

;; Check if delegation is active
(define-read-only (is-delegated (delegator principal))
    (match (map-get? delegations { delegator: delegator })
        delegation (ok (get active delegation))
        (ok false)
    )
)

;; Get effective voting power (own + delegated)
(define-read-only (get-effective-voting-power (voter principal))
    (let
        (
            (own-power u1)  ;; Base voting power
            (delegated (default-to { delegator-count: u0, total-power: u0, last-updated: u0 } (map-get? delegated-power { delegate: voter })))
        )
        (ok (+ own-power (get total-power delegated)))
    )
)

;; Get delegation history
(define-read-only (get-delegation-history (delegator principal) (index uint))
    (ok (map-get? delegation-history { delegator: delegator, index: index }))
)

;; Get total delegation count for delegator
(define-read-only (get-delegation-count (delegator principal))
    (ok (default-to u0 (map-get? delegation-count { delegator: delegator })))
)

;; Public functions

;; Delegate voting power to another user
(define-public (delegate-vote (delegate principal) (duration-seconds (optional uint)))
    (begin
        (asserts! (not (is-eq tx-sender delegate)) ERR-SELF-DELEGATION)

        ;; Check for circular delegation
        (asserts! (not (unwrap! (is-delegated delegate) ERR-INVALID-DELEGATE)) ERR-CIRCULAR-DELEGATION)

        ;; Check if already delegated
        (try! (match (map-get? delegations { delegator: tx-sender })
            existing-delegation
                (if (get active existing-delegation)
                    ERR-ALREADY-DELEGATED
                    (ok true)
                )
            (ok true)
        ))

        (let
            (
                (expires-at (match duration-seconds
                    duration (some (+ stacks-block-time duration))
                    none
                ))
                (history-index (default-to u0 (map-get? delegation-count { delegator: tx-sender })))
            )
            ;; Create delegation
            (map-set delegations
                { delegator: tx-sender }
                {
                    delegate: delegate,
                    delegated-at: stacks-block-time,         ;; Clarity 4: Unix timestamp
                    expires-at: expires-at,
                    active: true
                }
            )

            ;; Update delegated power for delegate
            (match (map-get? delegated-power { delegate: delegate })
                existing-power
                    (map-set delegated-power
                        { delegate: delegate }
                        {
                            delegator-count: (+ (get delegator-count existing-power) u1),
                            total-power: (+ (get total-power existing-power) u1),
                            last-updated: stacks-block-time  ;; Clarity 4: Unix timestamp
                        }
                    )
                ;; First delegation to this delegate
                (map-set delegated-power
                    { delegate: delegate }
                    {
                        delegator-count: u1,
                        total-power: u1,
                        last-updated: stacks-block-time      ;; Clarity 4: Unix timestamp
                    }
                )
            )

            ;; Record in history
            (map-set delegation-history
                { delegator: tx-sender, index: history-index }
                {
                    delegate: delegate,
                    delegated-at: stacks-block-time,
                    revoked-at: none
                }
            )

            (map-set delegation-count { delegator: tx-sender } (+ history-index u1))

            (print {
                event: "vote-delegated",
                delegator: tx-sender,
                delegate: delegate,
                timestamp: stacks-block-time,
                expires-at: expires-at
            })
            (ok true)
        )
    )
)

;; Revoke delegation
(define-public (revoke-delegation)
    (let
        (
            (delegation (unwrap! (map-get? delegations { delegator: tx-sender }) ERR-NOT-DELEGATED))
        )
        (asserts! (get active delegation) ERR-NOT-DELEGATED)

        (let
            (
                (delegate (get delegate delegation))
                (history-index (- (default-to u1 (map-get? delegation-count { delegator: tx-sender })) u1))
            )
            ;; Mark delegation as inactive
            (map-set delegations
                { delegator: tx-sender }
                (merge delegation { active: false })
            )

            ;; Update delegated power for delegate
            (match (map-get? delegated-power { delegate: delegate })
                existing-power
                    (map-set delegated-power
                        { delegate: delegate }
                        {
                            delegator-count: (- (get delegator-count existing-power) u1),
                            total-power: (- (get total-power existing-power) u1),
                            last-updated: stacks-block-time  ;; Clarity 4: Unix timestamp
                        }
                    )
                true  ;; Should not happen, but handle gracefully
            )

            ;; Update history
            (match (map-get? delegation-history { delegator: tx-sender, index: history-index })
                history-entry
                    (map-set delegation-history
                        { delegator: tx-sender, index: history-index }
                        (merge history-entry { revoked-at: (some stacks-block-time) })
                    )
                true
            )

            (print {
                event: "delegation-revoked",
                delegator: tx-sender,
                delegate: delegate,
                timestamp: stacks-block-time
            })
            (ok true)
        )
    )
)

;; Check and expire delegations (can be called by anyone)
(define-public (expire-delegation (delegator principal))
    (let
        (
            (delegation (unwrap! (map-get? delegations { delegator: delegator }) ERR-NOT-DELEGATED))
        )
        (asserts! (get active delegation) ERR-NOT-DELEGATED)

        (match (get expires-at delegation)
            expiry-time
                (begin
                    (asserts! (>= stacks-block-time expiry-time) ERR-NOT-AUTHORIZED)

                    (let
                        (
                            (delegate (get delegate delegation))
                        )
                        ;; Mark as inactive
                        (map-set delegations
                            { delegator: delegator }
                            (merge delegation { active: false })
                        )

                        ;; Update delegated power
                        (match (map-get? delegated-power { delegate: delegate })
                            existing-power
                                (map-set delegated-power
                                    { delegate: delegate }
                                    {
                                        delegator-count: (- (get delegator-count existing-power) u1),
                                        total-power: (- (get total-power existing-power) u1),
                                        last-updated: stacks-block-time
                                    }
                                )
                            true
                        )

                        (print {
                            event: "delegation-expired",
                            delegator: delegator,
                            delegate: delegate,
                            timestamp: stacks-block-time
                        })
                        (ok true)
                    )
                )
            ERR-NOT-AUTHORIZED  ;; No expiry set
        )
    )
)

Functions (9)

FunctionAccessArgs
get-delegationread-onlydelegator: principal
get-delegated-powerread-onlydelegate: principal
is-delegatedread-onlydelegator: principal
get-effective-voting-powerread-onlyvoter: principal
get-delegation-historyread-onlydelegator: principal, index: uint
get-delegation-countread-onlydelegator: principal
delegate-votepublicdelegate: principal, duration-seconds: (optional uint
revoke-delegationpublic
expire-delegationpublicdelegator: principal