Source Code

;; VoteChain: A decentralized voting system

;; Constants
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_NOT_AUTHORIZED (err u100))
(define-constant ERR_ALREADY_VOTED (err u101))
(define-constant ERR_PROPOSAL_NOT_FOUND (err u102))
(define-constant ERR_VOTING_CLOSED (err u103))
(define-constant ERR_INVALID_TITLE (err u104))
(define-constant ERR_INVALID_DESCRIPTION (err u105))
(define-constant ERR_INVALID_DURATION (err u106))

;; Data maps
(define-map proposals
  { proposal-id: uint }
  { 
    title: (string-ascii 50),
    description: (string-ascii 280),
    creator: principal,
    yes-votes: uint,
    no-votes: uint,
    end-block: uint
  }
)

(define-map votes
  { voter: principal, proposal-id: uint }
  { vote: bool }
)

;; Variables
(define-data-var proposal-nonce uint u0)

;; Read-only functions
(define-read-only (get-proposal (proposal-id uint))
  (map-get? proposals { proposal-id: proposal-id })
)

(define-read-only (get-proposal-count)
  (var-get proposal-nonce)
)

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

(define-read-only (is-voting-open (proposal-id uint))
  (let ((proposal (unwrap! (get-proposal proposal-id) false)))
    (< stacks-block-height (get end-block proposal))
  )
)

;; Private functions
(define-private (validate-string-length (str (string-ascii 280)) (min uint) (max uint))
  (let ((len (len str)))
    (and (>= len min) (<= len max))
  )
)

;; Public functions
(define-public (create-proposal (title (string-ascii 50)) (description (string-ascii 280)) (duration uint))
  (let
    (
      (new-proposal-id (+ (var-get proposal-nonce) u1))
      (end-block (+ stacks-block-height duration))
    )
    ;; Input validation
    (asserts! (validate-string-length title u1 u50) ERR_INVALID_TITLE)
    (asserts! (validate-string-length description u1 u280) ERR_INVALID_DESCRIPTION)
    (asserts! (and (> duration u0) (<= duration u10000)) ERR_INVALID_DURATION)
    
    (map-set proposals
      { proposal-id: new-proposal-id }
      {
        title: title,
        description: description,
        creator: tx-sender,
        yes-votes: u0,
        no-votes: u0,
        end-block: end-block
      }
    )
    (var-set proposal-nonce new-proposal-id)
    (ok new-proposal-id)
  )
)

(define-public (vote (proposal-id uint) (vote-bool bool))
  (let
    (
      (proposal (unwrap! (get-proposal proposal-id) ERR_PROPOSAL_NOT_FOUND))
    )
    (asserts! (is-voting-open proposal-id) ERR_VOTING_CLOSED)
    (asserts! (is-none (get-vote tx-sender proposal-id)) ERR_ALREADY_VOTED)
    (map-set votes
      { voter: tx-sender, proposal-id: proposal-id }
      { vote: vote-bool }
    )
    (if vote-bool
      (map-set proposals
        { proposal-id: proposal-id }
        (merge proposal { yes-votes: (+ (get yes-votes proposal) u1) })
      )
      (map-set proposals
        { proposal-id: proposal-id }
        (merge proposal { no-votes: (+ (get no-votes proposal) u1) })
      )
    )
    (ok true)
  )
)

;; Admin functions
(define-public (close-voting (proposal-id uint))
  (let
    (
      (proposal (unwrap! (get-proposal proposal-id) ERR_PROPOSAL_NOT_FOUND))
    )
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (asserts! (>= stacks-block-height (get end-block proposal)) ERR_VOTING_CLOSED)
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal { end-block: stacks-block-height })
    )
    (ok true)
  )
)

Functions (8)

FunctionAccessArgs
get-proposalread-onlyproposal-id: uint
get-proposal-countread-only
get-voteread-onlyvoter: principal, proposal-id: uint
is-voting-openread-onlyproposal-id: uint
validate-string-lengthprivatestr: (string-ascii 280
create-proposalpublictitle: (string-ascii 50
votepublicproposal-id: uint, vote-bool: bool
close-votingpublicproposal-id: uint