Source Code

;; proposal-voting.clar
;; Vote casting, tallying, and quorum checks

;; Error codes
(define-constant ERR-PROPOSAL-NOT-FOUND (err u6001))
(define-constant ERR-PROPOSAL-NOT-ACTIVE (err u6002))
(define-constant ERR-ALREADY-VOTED (err u6003))
(define-constant ERR-VOTING-NOT-ENDED (err u6005))
(define-constant ERR-NO-VOTING-POWER (err u6007))
(define-constant ERR-INVALID-VOTE-OPTION (err u6008))

;; Vote options
(define-constant VOTE-FOR u1)
(define-constant VOTE-AGAINST u2)
(define-constant VOTE-ABSTAIN u3)

;; Configuration
(define-constant QUORUM-THRESHOLD u1000000000) ;; 1000 STX quorum
(define-constant APPROVAL-THRESHOLD u5100) ;; 51% approval required (in basis points)

;; Data maps
(define-map proposal-votes
  uint  ;; proposal-id
  {
    votes-for: uint,
    votes-against: uint,
    votes-abstain: uint,
    total-votes: uint,
    voter-count: uint
  }
)

(define-map voter-records
  {proposal-id: uint, voter: principal}
  {
    vote: uint,
    power: uint,
    voted-at: uint
  }
)

;; Read-only functions

(define-read-only (get-proposal-votes (proposal-id uint))
  (default-to 
    {votes-for: u0, votes-against: u0, votes-abstain: u0, total-votes: u0, voter-count: u0}
    (map-get? proposal-votes proposal-id)
  )
)

(define-read-only (get-voter-record (proposal-id uint) (voter principal))
  (map-get? voter-records {proposal-id: proposal-id, voter: voter})
)

(define-read-only (has-voted (proposal-id uint) (voter principal))
  (is-some (map-get? voter-records {proposal-id: proposal-id, voter: voter}))
)

(define-read-only (is-quorum-reached (proposal-id uint))
  (let
    (
      (votes (get-proposal-votes proposal-id))
    )
    (>= (get total-votes votes) QUORUM-THRESHOLD)
  )
)

(define-read-only (is-proposal-passed (proposal-id uint))
  (let
    (
      (votes (get-proposal-votes proposal-id))
      (total-decisive (+ (get votes-for votes) (get votes-against votes)))
    )
    (if (is-eq total-decisive u0)
      false
      (and 
        (is-quorum-reached proposal-id)
        (>= (* (get votes-for votes) u10000) (* total-decisive APPROVAL-THRESHOLD))
      )
    )
  )
)

(define-read-only (get-approval-percentage (proposal-id uint))
  (let
    (
      (votes (get-proposal-votes proposal-id))
      (total-decisive (+ (get votes-for votes) (get votes-against votes)))
    )
    (if (is-eq total-decisive u0)
      u0
      (/ (* (get votes-for votes) u10000) total-decisive)
    )
  )
)

;; Input checks (satisfy check_checker)
(define-private (check-uint (n uint))
  (ok (asserts! (>= n u0) ERR-PROPOSAL-NOT-ACTIVE))
)

;; Resolve vote weight from governance-token snapshots when available.
;; If missing, snapshot voting power once for this voter/proposal.
(define-private (resolve-voting-power (proposal-id uint) (voter principal))
  (match (contract-call? .governance-token-v2-c4 get-snapshot-power voter proposal-id)
    snapshot-power (ok snapshot-power)
    (as-contract? ()
      (try! (contract-call? .governance-token-v2-c4 snapshot-voting-power voter proposal-id))
    )
  )
)

;; Public functions

;; Cast a vote
(define-public (vote (proposal-id uint) (vote-option uint))
  (let
    (
      (voter tx-sender)
      (current-votes (get-proposal-votes proposal-id))
    )
    ;; Check proposal is active
    (try! (check-uint proposal-id))
    (asserts! (contract-call? .proposal-submission-v2-c4 is-proposal-active proposal-id) ERR-PROPOSAL-NOT-ACTIVE)
    
    ;; Check hasn't voted
    (asserts! (not (has-voted proposal-id voter)) ERR-ALREADY-VOTED)
    
    ;; Check vote option is valid
    (asserts! (or
      (is-eq vote-option VOTE-FOR)
      (is-eq vote-option VOTE-AGAINST)
      (is-eq vote-option VOTE-ABSTAIN)
    ) ERR-INVALID-VOTE-OPTION)

    (let
      (
        (voting-power (try! (resolve-voting-power proposal-id voter)))
      )
      ;; Check has voting power
      (asserts! (> voting-power u0) ERR-NO-VOTING-POWER)
      
      ;; Record vote
      (map-set voter-records 
        {proposal-id: proposal-id, voter: voter}
        {
          vote: vote-option,
          power: voting-power,
          voted-at: stacks-block-height
        }
      )
      
      ;; Update vote tallies
      (map-set proposal-votes proposal-id
        (merge current-votes {
          votes-for: (if (is-eq vote-option VOTE-FOR) 
            (+ (get votes-for current-votes) voting-power) 
            (get votes-for current-votes)),
          votes-against: (if (is-eq vote-option VOTE-AGAINST) 
            (+ (get votes-against current-votes) voting-power) 
            (get votes-against current-votes)),
          votes-abstain: (if (is-eq vote-option VOTE-ABSTAIN) 
            (+ (get votes-abstain current-votes) voting-power) 
            (get votes-abstain current-votes)),
          total-votes: (+ (get total-votes current-votes) voting-power),
          voter-count: (+ (get voter-count current-votes) u1)
        })
      )
      
      (print {
        event: "vote-cast",
        proposal-id: proposal-id,
        voter: voter,
        vote: vote-option,
        power: voting-power
      })
      (ok true)
    )
  )
)

;; Conclude voting and determine outcome
(define-public (conclude (proposal-id uint))
  (let
    (
      (proposal-data (unwrap! (contract-call? .proposal-submission-v2-c4 get-proposal proposal-id) ERR-PROPOSAL-NOT-FOUND))
      (passed (is-proposal-passed proposal-id))
      (new-status (if passed u3 u4)) ;; STATUS-PASSED or STATUS-REJECTED
    )
    ;; Check voting period has ended
    (asserts! (> stacks-block-height (get end-block proposal-data)) ERR-VOTING-NOT-ENDED)
    
    ;; Update proposal status
    (try! (as-contract? ()
      (try! (contract-call? .proposal-submission-v2-c4 set-proposal-status proposal-id new-status))
      true
    ))
    
    ;; If passed and has proposal contract, execute it
    (if (and passed (is-some (get proposal-contract proposal-data)))
      (begin
        (print {
          event: "proposal-concluded",
          proposal-id: proposal-id,
          passed: passed,
          executable: true
        })
        (ok passed)
      )
      (begin
        (print {
          event: "proposal-concluded",
          proposal-id: proposal-id,
          passed: passed,
          executable: false
        })
        (ok passed)
      )
    )
  )
)

;; Extension callback
(define-public (callback (sender principal) (memo (buff 34)))
  (begin
    sender
    memo
    (ok true)
  )
)

Functions (11)

FunctionAccessArgs
get-proposal-votesread-onlyproposal-id: uint
get-voter-recordread-onlyproposal-id: uint, voter: principal
has-votedread-onlyproposal-id: uint, voter: principal
is-quorum-reachedread-onlyproposal-id: uint
is-proposal-passedread-onlyproposal-id: uint
get-approval-percentageread-onlyproposal-id: uint
check-uintprivaten: uint
resolve-voting-powerprivateproposal-id: uint, voter: principal
votepublicproposal-id: uint, vote-option: uint
concludepublicproposal-id: uint
callbackpublicsender: principal, memo: (buff 34