;; 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
)