Source Code

;; B2S Governance DAO
;; On-chain voting system for protocol decisions

(define-constant contract-owner tx-sender)

;; Error codes
(define-constant err-not-authorized (err u401))
(define-constant err-proposal-not-found (err u404))
(define-constant err-already-voted (err u405))
(define-constant err-voting-closed (err u406))
(define-constant err-insufficient-stake (err u407))
(define-constant err-proposal-not-passed (err u408))

;; Constants
(define-constant min-stake-to-propose u10000000000) ;; 10,000 tokens
(define-constant voting-period u1008) ;; ~7 days (144 blocks/day)
(define-constant quorum-percentage u20) ;; 20% of staked tokens
(define-constant approval-threshold u51) ;; 51% approval needed

;; Data vars
(define-data-var proposal-count uint u0)
(define-data-var total-voting-power uint u0)

;; Proposal data structure
(define-map proposals uint {
  proposer: principal,
  title: (string-ascii 100),
  description: (string-utf8 500),
  start-block: uint,
  end-block: uint,
  yes-votes: uint,
  no-votes: uint,
  executed: bool,
  category: (string-ascii 20)
})

;; Track who voted on which proposal
(define-map votes {proposal-id: uint, voter: principal} {
  vote: bool,
  voting-power: uint
})

;; User voting power (based on staked amount)
(define-map voting-power principal uint)

;; Public Functions

;; Create a proposal
(define-public (create-proposal 
  (title (string-ascii 100))
  (description (string-utf8 500))
  (category (string-ascii 20)))
  (let (
    (proposer tx-sender)
    (proposer-stake (default-to u0 (map-get? voting-power proposer)))
    (proposal-id (+ (var-get proposal-count) u1))
    (start-block block-height)
    (end-block (+ block-height voting-period))
  )
    ;; Check minimum stake
    (asserts! (>= proposer-stake min-stake-to-propose) err-insufficient-stake)
    
    ;; Create proposal
    (map-set proposals proposal-id {
      proposer: proposer,
      title: title,
      description: description,
      start-block: start-block,
      end-block: end-block,
      yes-votes: u0,
      no-votes: u0,
      executed: false,
      category: category
    })
    
    (var-set proposal-count proposal-id)
    (ok proposal-id)
  )
)

;; Cast a vote
(define-public (cast-vote (proposal-id uint) (vote-yes bool))
  (let (
    (voter tx-sender)
    (voter-power (default-to u0 (map-get? voting-power voter)))
    (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
    (vote-key {proposal-id: proposal-id, voter: voter})
  )
    ;; Checks
    (asserts! (> voter-power u0) err-insufficient-stake)
    (asserts! (is-none (map-get? votes vote-key)) err-already-voted)
    (asserts! (<= block-height (get end-block proposal)) err-voting-closed)
    
    ;; Record vote
    (map-set votes vote-key {
      vote: vote-yes,
      voting-power: voter-power
    })
    
    ;; Update proposal vote counts
    (map-set proposals proposal-id (merge proposal {
      yes-votes: (if vote-yes 
        (+ (get yes-votes proposal) voter-power)
        (get yes-votes proposal)),
      no-votes: (if vote-yes
        (get no-votes proposal)
        (+ (get no-votes proposal) voter-power))
    }))
    
    (ok true)
  )
)

;; Execute passed proposal
(define-public (execute-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
  )
    ;; Checks
    (asserts! (>= block-height (get end-block proposal)) err-voting-closed)
    (asserts! (not (get executed proposal)) err-proposal-not-passed)
    
    ;; Check if proposal passed
    (asserts! (has-proposal-passed proposal-id) err-proposal-not-passed)
    
    ;; Mark as executed
    (map-set proposals proposal-id (merge proposal {executed: true}))
    
    ;; Execute proposal logic here
    (ok true)
  )
)

;; Set voting power (called by staking contract)
(define-public (set-voting-power (user principal) (power uint))
  (begin
    ;; In production, check this is called by authorized contract
    (map-set voting-power user power)
    (ok true)
  )
)

;; Read-only functions

;; Get proposal details
(define-read-only (get-proposal (proposal-id uint))
  (ok (map-get? proposals proposal-id))
)

;; Get vote
(define-read-only (get-vote (proposal-id uint) (voter principal))
  (ok (map-get? votes {proposal-id: proposal-id, voter: voter}))
)

;; Get voting power
(define-read-only (get-voting-power (user principal))
  (ok (default-to u0 (map-get? voting-power user)))
)

;; Check if proposal passed
(define-read-only (has-proposal-passed (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal
      (let (
        (total-votes (+ (get yes-votes proposal) (get no-votes proposal)))
        (yes-percentage (if (> total-votes u0)
          (/ (* (get yes-votes proposal) u100) total-votes)
          u0))
        (quorum-met (>= total-votes (/ (* (var-get total-voting-power) quorum-percentage) u100)))
        (approval-met (>= yes-percentage approval-threshold))
      )
        (and quorum-met approval-met)
      )
    false
  )
)

;; Get proposal status
(define-read-only (get-proposal-status (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal
      (if (get executed proposal)
        (ok "executed")
        (if (> block-height (get end-block proposal))
          (if (has-proposal-passed proposal-id)
            (ok "passed")
            (ok "failed"))
          (ok "active")))
    (err err-proposal-not-found)
  )
)

;; Get total proposals
(define-read-only (get-proposal-count)
  (ok (var-get proposal-count))
)

;; Calculate voting results
(define-read-only (get-voting-results (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal
      (let (
        (total (+ (get yes-votes proposal) (get no-votes proposal)))
        (yes-pct (if (> total u0) (/ (* (get yes-votes proposal) u100) total) u0))
        (no-pct (if (> total u0) (/ (* (get no-votes proposal) u100) total) u0))
      )
        (ok {
          yes-votes: (get yes-votes proposal),
          no-votes: (get no-votes proposal),
          total-votes: total,
          yes-percentage: yes-pct,
          no-percentage: no-pct
        })
      )
    (err err-proposal-not-found)
  )
)

Functions (11)

FunctionAccessArgs
create-proposalpublictitle: (string-ascii 100
cast-votepublicproposal-id: uint, vote-yes: bool
execute-proposalpublicproposal-id: uint
set-voting-powerpublicuser: principal, power: uint
get-proposalread-onlyproposal-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
get-voting-powerread-onlyuser: principal
has-proposal-passedread-onlyproposal-id: uint
get-proposal-statusread-onlyproposal-id: uint
get-proposal-countread-only
get-voting-resultsread-onlyproposal-id: uint