;; token-voting.clar
;; Token-weighted voting system with delegation and quadratic option
;; Clarity 4 / Epoch 3.3
;; -----------------------------------------------
;; Constants
;; -----------------------------------------------
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u300))
(define-constant ERR-PROPOSAL-NOT-FOUND (err u301))
(define-constant ERR-ALREADY-VOTED (err u302))
(define-constant ERR-VOTING-CLOSED (err u303))
(define-constant ERR-NO-VOTING-POWER (err u304))
(define-constant ERR-PROPOSAL-NOT-PASSED (err u305))
(define-constant ERR-ALREADY-EXECUTED (err u306))
(define-constant ERR-INVALID-INPUT (err u307))
(define-constant ERR-SELF-DELEGATE (err u308))
(define-constant ERR-VOTING-ACTIVE (err u309))
;; -----------------------------------------------
;; Data Variables
;; -----------------------------------------------
(define-data-var proposal-nonce uint u0)
(define-data-var quadratic-enabled bool false)
(define-data-var default-voting-period uint u144)
;; -----------------------------------------------
;; Data Maps
;; -----------------------------------------------
(define-map proposals
uint
{
title: (string-ascii 64),
description: (string-ascii 256),
proposer: principal,
start-block: uint,
end-block: uint,
yes-votes: uint,
no-votes: uint,
executed: bool,
total-voters: uint
}
)
(define-map votes
{ proposal-id: uint, voter: principal }
{
amount: uint,
vote-for: bool,
block: uint
}
)
(define-map token-balances
principal
uint
)
(define-map delegations
principal
principal
)
(define-map vote-history
principal
(list 20 uint)
)
;; -----------------------------------------------
;; Private Functions
;; -----------------------------------------------
(define-private (sqrt-approx (n uint))
(if (<= n u1)
n
(let (
(x1 (/ n u2))
(x2 (/ (+ x1 (/ n x1)) u2))
(x3 (/ (+ x2 (/ n x2)) u2))
)
x3
)
)
)
(define-private (get-voting-power (voter principal))
(let (
(own-balance (default-to u0 (map-get? token-balances voter)))
)
(if (var-get quadratic-enabled)
(sqrt-approx own-balance)
own-balance
)
)
)
(define-private (append-to-history (who principal) (pid uint))
(let (
(current (default-to (list) (map-get? vote-history who)))
)
(match (as-max-len? (append current pid) u20)
updated (begin (map-set vote-history who updated) true)
false
)
)
)
;; -----------------------------------------------
;; Public Functions
;; -----------------------------------------------
;; Assign token balance to a voter (admin only)
(define-public (set-token-balance (who principal) (amount uint))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
(map-set token-balances who amount)
(ok true)
)
)
;; Create a new proposal
(define-public (create-proposal
(title (string-ascii 64))
(description (string-ascii 256))
(duration uint))
(let (
(pid (var-get proposal-nonce))
(vote-period (if (> duration u0) duration (var-get default-voting-period)))
)
(asserts! (> (get-voting-power tx-sender) u0) ERR-NO-VOTING-POWER)
(map-set proposals pid
{
title: title,
description: description,
proposer: tx-sender,
start-block: tenure-height,
end-block: (+ tenure-height vote-period),
yes-votes: u0,
no-votes: u0,
executed: false,
total-voters: u0
})
(var-set proposal-nonce (+ pid u1))
(ok pid)
)
)
;; Cast a vote on a proposal
(define-public (cast-vote (proposal-id uint) (vote-for bool))
(let (
(proposal (unwrap! (map-get? proposals proposal-id)
ERR-PROPOSAL-NOT-FOUND))
(effective-voter (default-to tx-sender
(map-get? delegations tx-sender)))
(power (get-voting-power effective-voter))
)
(asserts! (<= tenure-height (get end-block proposal)) ERR-VOTING-CLOSED)
(asserts! (> power u0) ERR-NO-VOTING-POWER)
(asserts! (is-none (map-get? votes
{ proposal-id: proposal-id, voter: tx-sender }))
ERR-ALREADY-VOTED)
(map-set votes
{ proposal-id: proposal-id, voter: tx-sender }
{ amount: power, vote-for: vote-for, block: tenure-height })
(if vote-for
(map-set proposals proposal-id
(merge proposal {
yes-votes: (+ (get yes-votes proposal) power),
total-voters: (+ (get total-voters proposal) u1) }))
(map-set proposals proposal-id
(merge proposal {
no-votes: (+ (get no-votes proposal) power),
total-voters: (+ (get total-voters proposal) u1) }))
)
(append-to-history tx-sender proposal-id)
(ok power)
)
)
;; Delegate voting power to another address
(define-public (delegate-to (delegate principal))
(begin
(asserts! (not (is-eq tx-sender delegate)) ERR-SELF-DELEGATE)
(map-set delegations tx-sender delegate)
(ok true)
)
)
;; Remove delegation
(define-public (revoke-delegation)
(begin
(map-delete delegations tx-sender)
(ok true)
)
)
;; Toggle quadratic voting
(define-public (set-quadratic-voting (enabled bool))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
(var-set quadratic-enabled enabled)
(ok true)
)
)
;; Execute a passed proposal
(define-public (execute-proposal (proposal-id uint))
(let (
(proposal (unwrap! (map-get? proposals proposal-id)
ERR-PROPOSAL-NOT-FOUND))
)
(asserts! (> tenure-height (get end-block proposal)) ERR-VOTING-ACTIVE)
(asserts! (not (get executed proposal)) ERR-ALREADY-EXECUTED)
(asserts! (> (get yes-votes proposal) (get no-votes proposal))
ERR-PROPOSAL-NOT-PASSED)
(map-set proposals proposal-id
(merge proposal { executed: true }))
(ok true)
)
)
;; -----------------------------------------------
;; Read-Only Functions
;; -----------------------------------------------
(define-read-only (get-proposal (proposal-id uint))
(map-get? proposals proposal-id)
)
(define-read-only (get-vote (proposal-id uint) (voter principal))
(map-get? votes { proposal-id: proposal-id, voter: voter })
)
(define-read-only (get-voter-power (who principal))
(get-voting-power who)
)
(define-read-only (get-vote-history (who principal))
(default-to (list) (map-get? vote-history who))
)
(define-read-only (get-delegation (who principal))
(map-get? delegations who)
)
(define-read-only (get-proposal-count)
(var-get proposal-nonce)
)
(define-read-only (is-quadratic-enabled)
(var-get quadratic-enabled)
)