From 5758482ec4a5289b1c175669495f41d621864078 Mon Sep 17 00:00:00 2001 From: Syed Ghufran Hassan Date: Thu, 26 Feb 2026 13:48:02 +0500 Subject: [PATCH] feat(governance): upgrade to v8 with execution, cancellation, vote changes & dynamic config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added proposal cancellation (STATUS_CANCELLED) - Added proposal execution flow (PASSED → EXECUTED) - Enabled vote changes before deadline - Implemented early finalization when quorum is met - Converted governance parameters to mutable config - Added optional proposal deposit (anti-spam) - Added governance event prints for indexing - Improved status validation and error handling This upgrade enhances flexibility, security, and production readiness of the StackSusu governance module. --- contracts/stacksusu-governance-v7.clar | 206 ++++++++++++++++++------- 1 file changed, 149 insertions(+), 57 deletions(-) diff --git a/contracts/stacksusu-governance-v7.clar b/contracts/stacksusu-governance-v7.clar index e038392..74e55cf 100644 --- a/contracts/stacksusu-governance-v7.clar +++ b/contracts/stacksusu-governance-v7.clar @@ -1,33 +1,52 @@ -;; StackSusu Governance v7 -;; Simplified proposal and voting system +;; StackSusu Governance v8 +;; Enhanced Governance System (define-constant CONTRACT-OWNER tx-sender) -;; Error constants +;; ============================= +;; Errors +;; ============================= + (define-constant ERR-NOT-AUTHORIZED (err u7000)) (define-constant ERR-PROPOSAL-NOT-FOUND (err u7001)) (define-constant ERR-ALREADY-VOTED (err u7002)) (define-constant ERR-VOTING-CLOSED (err u7003)) (define-constant ERR-VOTING-OPEN (err u7004)) (define-constant ERR-THRESHOLD-NOT-MET (err u7005)) +(define-constant ERR-NOT-PROPOSER (err u7006)) +(define-constant ERR-INVALID-STATUS (err u7007)) + +;; ============================= +;; Status +;; ============================= -;; Proposal status (define-constant STATUS-ACTIVE u0) (define-constant STATUS-PASSED u1) (define-constant STATUS-FAILED u2) (define-constant STATUS-EXECUTED u3) +(define-constant STATUS-CANCELLED u4) + +;; ============================= +;; Config (Now Mutable) +;; ============================= -;; Configuration -(define-constant VOTING-PERIOD u1008) ;; ~7 days -(define-constant QUORUM-THRESHOLD u10) ;; Minimum 10 votes -(define-constant PASS-THRESHOLD u51) ;; 51% to pass +(define-data-var voting-period uint u1008) +(define-data-var quorum-threshold uint u10) +(define-data-var pass-threshold uint u51) +(define-data-var proposal-deposit uint u0) +;; ============================= ;; State +;; ============================= + (define-data-var proposal-counter uint u0) -;; Proposals +;; ============================= +;; Maps +;; ============================= + (define-map proposals - uint ;; proposal-id + uint { proposer: principal, title: (string-ascii 100), @@ -36,26 +55,46 @@ votes-against: uint, status: uint, created-at: uint, - ends-at: uint + ends-at: uint, + deposit: uint } ) -;; Votes (define-map votes { proposal-id: uint, voter: principal } - bool ;; true = for, false = against + bool ) +;; ============================= +;; Governance Config Updates +;; ============================= + +(define-public (update-config (new-quorum uint) (new-pass uint) (new-period uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED) + (var-set quorum-threshold new-quorum) + (var-set pass-threshold new-pass) + (var-set voting-period new-period) + (ok true) + ) +) -;; ============================================ -;; Proposal Functions -;; ============================================ +;; ============================= +;; Create Proposal +;; ============================= (define-public (create-proposal (title (string-ascii 100)) (description (string-ascii 500))) (let ( (proposal-id (+ (var-get proposal-counter) u1)) + (deposit (var-get proposal-deposit)) ) + ;; Optional anti-spam deposit + (if (> deposit u0) + (stx-transfer? deposit tx-sender (as-contract tx-sender)) + (ok true) + ) + (map-set proposals proposal-id { proposer: tx-sender, title: title, @@ -64,81 +103,134 @@ votes-against: u0, status: STATUS-ACTIVE, created-at: block-height, - ends-at: (+ block-height VOTING-PERIOD) + ends-at: (+ block-height (var-get voting-period)), + deposit: deposit }) - + (var-set proposal-counter proposal-id) + + (print { event: "proposal-created", id: proposal-id }) (ok proposal-id) ) ) +;; ============================= +;; Vote (Now Changeable) +;; ============================= + (define-public (vote (proposal-id uint) (vote-for bool)) (let ( (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND)) - (already-voted (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) + (previous-vote (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) ) (asserts! (is-eq (get status proposal) STATUS-ACTIVE) ERR-VOTING-CLOSED) (asserts! (< block-height (get ends-at proposal)) ERR-VOTING-CLOSED) - (asserts! (is-none already-voted) ERR-ALREADY-VOTED) - - ;; Record vote + + ;; Adjust counts if changing vote + (map-set proposals proposal-id + (merge proposal { + votes-for: + (if vote-for + (+ (get votes-for proposal) + (if (is-some previous-vote) + (if (unwrap-panic previous-vote) u0 u1) + u1)) + (if (is-some previous-vote) + (if (unwrap-panic previous-vote) (- (get votes-for proposal) u1) (get votes-for proposal)) + (get votes-for proposal))), + + votes-against: + (if vote-for + (if (is-some previous-vote) + (if (unwrap-panic previous-vote) (get votes-against proposal) (- (get votes-against proposal) u1)) + (get votes-against proposal)) + (+ (get votes-against proposal) + (if (is-some previous-vote) + (if (unwrap-panic previous-vote) u1 u0) + u1))) + }) + ) + (map-set votes { proposal-id: proposal-id, voter: tx-sender } vote-for) - - ;; Update vote counts - (map-set proposals proposal-id (merge proposal { - votes-for: (if vote-for (+ (get votes-for proposal) u1) (get votes-for proposal)), - votes-against: (if vote-for (get votes-against proposal) (+ (get votes-against proposal) u1)) - })) - + + (print { event: "vote-cast", proposal: proposal-id }) (ok true) ) ) +;; ============================= +;; Finalize (Early Allowed) +;; ============================= + (define-public (finalize-proposal (proposal-id uint)) (let ( (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND)) (total-votes (+ (get votes-for proposal) (get votes-against proposal))) - (passed (and - (>= total-votes QUORUM-THRESHOLD) - (> (* (get votes-for proposal) u100) (* total-votes PASS-THRESHOLD)))) + (quorum (var-get quorum-threshold)) + (pass-th (var-get pass-threshold)) + (passed (and + (>= total-votes quorum) + (> (* (get votes-for proposal) u100) + (* total-votes pass-th)))) ) - (asserts! (is-eq (get status proposal) STATUS-ACTIVE) ERR-VOTING-CLOSED) - (asserts! (>= block-height (get ends-at proposal)) ERR-VOTING-OPEN) - - (map-set proposals proposal-id (merge proposal { - status: (if passed STATUS-PASSED STATUS-FAILED) - })) - + (asserts! (is-eq (get status proposal) STATUS-ACTIVE) ERR-INVALID-STATUS) + + ;; Allow finalize early if quorum met + (asserts! + (or (>= block-height (get ends-at proposal)) + (>= total-votes quorum)) + ERR-VOTING-OPEN) + + (map-set proposals proposal-id + (merge proposal { + status: (if passed STATUS-PASSED STATUS-FAILED) + }) + ) + + (print { event: "proposal-finalized", id: proposal-id, passed: passed }) (ok passed) ) ) +;; ============================= +;; Execute Proposal +;; ============================= -;; ============================================ -;; Read Functions -;; ============================================ +(define-public (execute-proposal (proposal-id uint)) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND)) + ) + (asserts! (is-eq (get status proposal) STATUS-PASSED) ERR-INVALID-STATUS) -(define-read-only (get-proposal (proposal-id uint)) - (map-get? proposals proposal-id) -) + (map-set proposals proposal-id + (merge proposal { status: STATUS-EXECUTED }) + ) -(define-read-only (get-proposal-count) - (var-get proposal-counter) + (print { event: "proposal-executed", id: proposal-id }) + (ok true) + ) ) -(define-read-only (get-vote (proposal-id uint) (voter principal)) - (map-get? votes { proposal-id: proposal-id, voter: voter }) -) +;; ============================= +;; Cancel Proposal +;; ============================= -(define-read-only (has-voted (proposal-id uint) (voter principal)) - (is-some (map-get? votes { proposal-id: proposal-id, voter: voter })) -) +(define-public (cancel-proposal (proposal-id uint)) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND)) + ) + (asserts! (is-eq tx-sender (get proposer proposal)) ERR-NOT-PROPOSER) + (asserts! (is-eq (get status proposal) STATUS-ACTIVE) ERR-INVALID-STATUS) -(define-read-only (is-proposal-active (proposal-id uint)) - (match (map-get? proposals proposal-id) - p (and (is-eq (get status p) STATUS-ACTIVE) (< block-height (get ends-at p))) - false + (map-set proposals proposal-id + (merge proposal { status: STATUS-CANCELLED }) + ) + + (print { event: "proposal-cancelled", id: proposal-id }) + (ok true) ) )