Source Code

;; title: ProtoFlux
;; version:
;; summary:
;; description:

(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_UNAUTHORIZED (err u401))
(define-constant ERR_PROPOSAL_NOT_FOUND (err u404))
(define-constant ERR_PROPOSAL_EXPIRED (err u405))
(define-constant ERR_PROPOSAL_EXECUTED (err u406))
(define-constant ERR_ALREADY_VOTED (err u407))
(define-constant ERR_VOTING_PERIOD_ACTIVE (err u408))
(define-constant ERR_INSUFFICIENT_VOTES (err u409))

;; Data variables
(define-data-var proposal-counter uint u0)
(define-data-var voting-period uint u1008) ;; ~1 week in blocks
(define-data-var quorum-threshold uint u50) ;; 50% participation required
(define-data-var approval-threshold uint u60) ;; 60% approval required

;; Data maps
(define-map proposals 
  uint 
  {
    proposer: principal,
    title: (string-ascii 256),
    description: (string-ascii 1024),
    new-contract-address: principal,
    created-at: uint,
    executed: bool,
    yes-votes: uint,
    no-votes: uint,
    total-voters: uint
  }
)

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

(define-map voter-weights principal uint)

;; Public functions

;; Create a new upgrade proposal
(define-public (create-proposal (title (string-ascii 256)) 
                               (description (string-ascii 1024)) 
                               (new-contract-address principal))
  (let ((proposal-id (+ (var-get proposal-counter) u1)))
    (map-set proposals proposal-id {
      proposer: tx-sender,
      title: title,
      description: description,
      new-contract-address: new-contract-address,
      created-at: stacks-block-height,
      executed: false,
      yes-votes: u0,
      no-votes: u0,
      total-voters: u0
    })
    (var-set proposal-counter proposal-id)
    (ok proposal-id)
  )
)

;; Vote on a proposal
(define-public (vote (proposal-id uint) (support bool))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) ERR_PROPOSAL_NOT_FOUND))
        (voter-weight (default-to u1 (map-get? voter-weights tx-sender)))
        (existing-vote (map-get? votes {proposal-id: proposal-id, voter: tx-sender})))
    
    ;; Check if proposal is still active
    (asserts! (< stacks-block-height (+ (get created-at proposal) (var-get voting-period))) ERR_PROPOSAL_EXPIRED)
    
    ;; Check if proposal is not executed
    (asserts! (not (get executed proposal)) ERR_PROPOSAL_EXECUTED)
    
    ;; Check if voter hasn't voted already
    (asserts! (is-none existing-vote) ERR_ALREADY_VOTED)
    
    ;; Record the vote
    (map-set votes {proposal-id: proposal-id, voter: tx-sender} {
      vote: support,
      block-height: stacks-block-height
    })
    
    ;; Update proposal vote counts
    (map-set proposals proposal-id (merge proposal {
      yes-votes: (if support 
                    (+ (get yes-votes proposal) voter-weight) 
                    (get yes-votes proposal)),
      no-votes: (if support 
                   (get no-votes proposal) 
                   (+ (get no-votes proposal) voter-weight)),
      total-voters: (+ (get total-voters proposal) u1)
    }))
    
    (ok true)
  )
)

;; Execute a proposal if it passes
(define-public (execute-proposal (proposal-id uint))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) ERR_PROPOSAL_NOT_FOUND)))
    
    ;; Check if voting period has ended
    (asserts! (>= stacks-block-height (+ (get created-at proposal) (var-get voting-period))) ERR_VOTING_PERIOD_ACTIVE)
    
    ;; Check if proposal is not already executed
    (asserts! (not (get executed proposal)) ERR_PROPOSAL_EXECUTED)
    
    ;; Check quorum (minimum participation)
    (let ((total-weight (get-total-voting-weight))
          (participated-weight (+ (get yes-votes proposal) (get no-votes proposal))))
      (asserts! (>= (* participated-weight u100) (* total-weight (var-get quorum-threshold))) ERR_INSUFFICIENT_VOTES)
      
      ;; Check approval threshold
      (asserts! (>= (* (get yes-votes proposal) u100) (* participated-weight (var-get approval-threshold))) ERR_INSUFFICIENT_VOTES)
      
      ;; Mark as executed
      (map-set proposals proposal-id (merge proposal {executed: true}))
      
      ;; In a real implementation, this would trigger the contract upgrade
      ;; For now, we just emit the success
      (print {
        event: "proposal-executed",
        proposal-id: proposal-id,
        new-contract: (get new-contract-address proposal)
      })
      
      (ok true)
    )
  )
)

;; Admin function to set voter weight
(define-public (set-voter-weight (voter principal) (weight uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (map-set voter-weights voter weight)
    (ok true)
  )
)

;; Admin function to update voting period
(define-public (set-voting-period (new-period uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (var-set voting-period new-period)
    (ok true)
  )
)

;; Admin function to update quorum threshold
(define-public (set-quorum-threshold (new-threshold uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (asserts! (<= new-threshold u100) (err u400))
    (var-set quorum-threshold new-threshold)
    (ok true)
  )
)

;; Admin function to update approval threshold
(define-public (set-approval-threshold (new-threshold uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (asserts! (<= new-threshold u100) (err u400))
    (var-set approval-threshold new-threshold)
    (ok true)
  )
)

;; Read-only functions

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

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

;; Get voter weight
(define-read-only (get-voter-weight (voter principal))
  (default-to u1 (map-get? voter-weights voter))
)

;; Get current proposal counter
(define-read-only (get-proposal-counter)
  (var-get proposal-counter)
)

;; Get voting parameters
(define-read-only (get-voting-parameters)
  {
    voting-period: (var-get voting-period),
    quorum-threshold: (var-get quorum-threshold),
    approval-threshold: (var-get approval-threshold)
  }
)

;; Check if proposal can be executed
(define-read-only (can-execute-proposal (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (let ((voting-ended (>= stacks-block-height (+ (get created-at proposal) (var-get voting-period))))
                   (not-executed (not (get executed proposal)))
                   (total-weight (get-total-voting-weight))
                   (participated-weight (+ (get yes-votes proposal) (get no-votes proposal)))
                   (quorum-met (>= (* participated-weight u100) (* total-weight (var-get quorum-threshold))))
                   (approval-met (>= (* (get yes-votes proposal) u100) (* participated-weight (var-get approval-threshold)))))
              (and voting-ended not-executed quorum-met approval-met))
    false
  )
)

;; Helper function to calculate total voting weight
(define-read-only (get-total-voting-weight)
  ;; In a real implementation, this would sum all voter weights
  ;; For simplicity, we return a fixed value
  u1000
)

Functions (14)

FunctionAccessArgs
create-proposalpublictitle: (string-ascii 256
votepublicproposal-id: uint, support: bool
execute-proposalpublicproposal-id: uint
set-voter-weightpublicvoter: principal, weight: uint
set-voting-periodpublicnew-period: uint
set-quorum-thresholdpublicnew-threshold: uint
set-approval-thresholdpublicnew-threshold: uint
get-proposalread-onlyproposal-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
get-voter-weightread-onlyvoter: principal
get-proposal-counterread-only
get-voting-parametersread-only
can-execute-proposalread-onlyproposal-id: uint
get-total-voting-weightread-only