Source Code

;; StackFlow Governance Contract
;; Community-driven governance for the StackFlow ecosystem
;; FLOW holders can submit proposals and vote on protocol changes

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u300))
(define-constant err-not-found (err u301))
(define-constant err-already-voted (err u302))
(define-constant err-insufficient-flow (err u303))
(define-constant err-proposal-expired (err u304))
(define-constant err-proposal-not-passed (err u305))
(define-constant err-already-executed (err u306))
(define-constant err-voting-closed (err u307))
(define-constant err-invalid-proposal (err u308))

;; Token references
(define-constant flow-token 'SP3F4WEX90KZQ6D25TWP09J90D6CSYGW1JWXN5YF4.stackflow-flow-token)
(define-constant staking-contract 'SP3F4WEX90KZQ6D25TWP09J90D6CSYGW1JWXN5YF4.stackflow-staking)

;; Governance parameters
(define-constant min-proposal-stake u5000000000) ;; 5,000 FLOW required to submit proposal
(define-constant voting-period u1440) ;; ~10 days in blocks (assuming 10 min blocks)
(define-constant quorum-threshold u10000000000) ;; 10,000 FLOW minimum participation
(define-constant approval-threshold u6000) ;; 60% approval required (basis points)

;; State
(define-data-var proposal-nonce uint u0)
(define-data-var paused bool false)

;; Proposal types
(define-constant proposal-type-parameter "PARAM")
(define-constant proposal-type-strategy "STRATEGY")
(define-constant proposal-type-treasury "TREASURY")
(define-constant proposal-type-upgrade "UPGRADE")

;; Proposals
(define-map proposals uint {
  proposer: principal,
  title: (string-utf8 256),
  description: (string-utf8 1024),
  proposal-type: (string-ascii 8),
  parameter: (optional (string-ascii 32)),
  new-value: (optional uint),
  created-at: uint,
  voting-ends-at: uint,
  votes-for: uint,
  votes-against: uint,
  total-votes: uint,
  is-executed: bool,
  execution-block: (optional uint)
})

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

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

;; Read-only: Get user's vote on a proposal
(define-read-only (get-vote (proposal-id uint) (voter principal))
  (map-get? votes {proposal-id: proposal-id, voter: voter}))

;; Read-only: Check if proposal passed
(define-read-only (has-proposal-passed (proposal-id uint))
  (match (get-proposal proposal-id)
    proposal
      (let (
        (total (get total-votes proposal))
        (for (get votes-for proposal))
        (against (get votes-against proposal))
      )
        (ok (and
          (>= total quorum-threshold) ;; Quorum met
          (>= (/ (* for u10000) (+ for against)) approval-threshold)))) ;; Approval threshold met
    (err err-not-found)))

;; Submit a proposal
(define-public (submit-proposal 
    (title (string-utf8 256))
    (description (string-utf8 1024))
    (proposal-type (string-ascii 8))
    (parameter (optional (string-ascii 32)))
    (new-value (optional uint)))
  (let (
    (proposer tx-sender)
    (proposal-id (+ (var-get proposal-nonce) u1))
    (stake-info (contract-call? staking-contract get-stake proposer))
    (voting-power (match stake-info data (get amount data) u0))
  )
    (asserts! (not (var-get paused)) err-owner-only)
    (asserts! (>= voting-power min-proposal-stake) err-insufficient-flow)
    
    ;; Create proposal
    (map-set proposals proposal-id {
      proposer: proposer,
      title: title,
      description: description,
      proposal-type: proposal-type,
      parameter: parameter,
      new-value: new-value,
      created-at: stacks-block-height,
      voting-ends-at: (+ stacks-block-height voting-period),
      votes-for: u0,
      votes-against: u0,
      total-votes: u0,
      is-executed: false,
      execution-block: none
    })
    
    (var-set proposal-nonce proposal-id)
    
    (print {
      event: "proposal-submitted",
      proposal-id: proposal-id,
      proposer: proposer,
      title: title,
      type: proposal-type
    })
    
    (ok proposal-id)))

;; Vote on a proposal
(define-public (vote (proposal-id uint) (vote-for bool))
  (let (
    (voter tx-sender)
    (proposal (unwrap! (get-proposal proposal-id) err-not-found))
    (stake-info (contract-call? staking-contract get-stake voter))
    (voting-power (match stake-info data (get amount data) u0))
  )
    (asserts! (> voting-power u0) err-insufficient-flow)
    (asserts! (<= stacks-block-height (get voting-ends-at proposal)) err-voting-closed)
    (asserts! (is-none (get-vote proposal-id voter)) err-already-voted)
    
    ;; Record vote
    (map-set votes {proposal-id: proposal-id, voter: voter} {
      vote-power: voting-power,
      voted-for: vote-for
    })
    
    ;; Update proposal totals
    (map-set proposals proposal-id (merge proposal {
      votes-for: (if vote-for (+ (get votes-for proposal) voting-power) (get votes-for proposal)),
      votes-against: (if vote-for (get votes-against proposal) (+ (get votes-against proposal) voting-power)),
      total-votes: (+ (get total-votes proposal) voting-power)
    }))
    
    (print {
      event: "vote-cast",
      proposal-id: proposal-id,
      voter: voter,
      vote-for: vote-for,
      power: voting-power
    })
    
    (ok true)))

;; Execute a passed proposal (owner only for now, can be made permissionless)
(define-public (execute-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (get-proposal proposal-id) err-not-found))
  )
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> stacks-block-height (get voting-ends-at proposal)) err-voting-closed)
    (asserts! (not (get is-executed proposal)) err-already-executed)
    (asserts! (unwrap! (has-proposal-passed proposal-id) err-proposal-not-passed) err-proposal-not-passed)
    
    ;; Mark as executed
    (map-set proposals proposal-id (merge proposal {
      is-executed: true,
      execution-block: (some stacks-block-height)
    }))
    
    (print {
      event: "proposal-executed",
      proposal-id: proposal-id,
      type: (get proposal-type proposal)
    })
    
    ;; Note: Actual execution logic would be implemented based on proposal type
    ;; For now, this marks it as executed for governance tracking
    (ok true)))

;; Read-only utilities

(define-read-only (get-governance-stats)
  (ok {
    total-proposals: (var-get proposal-nonce),
    min-proposal-stake: min-proposal-stake,
    voting-period: voting-period,
    quorum-threshold: quorum-threshold,
    approval-threshold: approval-threshold,
    paused: (var-get paused)
  }))

(define-read-only (get-active-proposals)
  ;; Returns count of non-expired proposals
  (ok (var-get proposal-nonce)))

;; Admin functions

(define-public (pause-governance)
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set paused true)
    (print {event: "governance-paused"})
    (ok true)))

(define-public (unpause-governance)
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set paused false)
    (print {event: "governance-unpaused"})
    (ok true)))

Functions (10)

FunctionAccessArgs
get-proposalread-onlyproposal-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
has-proposal-passedread-onlyproposal-id: uint
submit-proposalpublictitle: (string-utf8 256
votepublicproposal-id: uint, vote-for: bool
execute-proposalpublicproposal-id: uint
get-governance-statsread-only
get-active-proposalsread-only
pause-governancepublic
unpause-governancepublic