Source Code


;; bitgov.clar
;; 
;; ============================================
;; title: BitGov
;; summary: A comprehensive DAO governance suite with proposal management, voting, and treasury.
;; ============================================

;; --- Constants & Error Codes ---

(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_ALREADY_EXISTS (err u101))
(define-constant ERR_NOT_FOUND (err u102))
(define-constant ERR_PROPOSAL_EXPIRED (err u103))
(define-constant ERR_PROPOSAL_NOT_ACTIVE (err u104))
(define-constant ERR_ALREADY_VOTED (err u105))
(define-constant ERR_INSUFFICIENT_FUNDS (err u106))
(define-constant ERR_INVALID_PARAMETER (err u107))
(define-constant ERR_EXECUTION_DELAY_NOT_MET (err u108))
(define-constant ERR_NOT_MEMBER (err u109))
(define-constant ERR_INSUFFICIENT_REPUTATION (err u110))

;; Governance Parameters
(define-constant VOTING_PERIOD u144) ;; ~1 day in blocks (assuming 10 min blocks)
(define-constant EXECUTION_DELAY u144) ;; ~1 day
(define-constant MIN_REPUTATION_TO_PROPOSE u10) ;; Minimum reputation needed to create proposals
(define-constant DEPLOYER_PRINCIPAL tx-sender) ;; Note: this captures runtime sender, usually deployment

;; --- Data Variables ---

(define-data-var proposal-count uint u0)
(define-data-var general-counter uint u0)

;; --- Data Maps ---

;; Proposals
;; Enhanced to include execution details for treasury and membership
(define-map proposals uint {
  proposer: principal,
  title: (string-ascii 50),
  description: (string-utf8 500),
  start-block: uint,
  end-block: uint,
  votes-for: uint,
  votes-against: uint,
  status: (string-ascii 20), ;; "active", "passed", "failed", "executed"
  executed: bool,
  ;; New fields for execution
  transfer-amount: uint,
  transfer-to: (optional principal),
  add-member: (optional principal) ;; If set, this proposal is to add a new member
})

;; Votes: proposal-id -> voter -> { outcome, amount }
(define-map votes { proposal-id: uint, voter: principal } {
  outcome: bool, ;; true = for, false = against
  amount: uint   ;; Reputation weight used
})

;; Members
(define-map members principal {
  reputation: uint,
  joined-at: uint
})

;; --- Public Functions (Governance) ---

;; 1. Create Proposal
;; Updated to support treasury transfers and membership additions
(define-public (create-proposal (title (string-ascii 50)) 
                                (description (string-utf8 500))
                                (transfer-amount uint)
                                (transfer-to (optional principal))
                                (add-member (optional principal)))
  (let
    (
      (current-count (var-get proposal-count))
      (start-height burn-block-height)
      (end-height (+ start-height VOTING_PERIOD))
      (proposer-info (unwrap! (map-get? members tx-sender) ERR_NOT_MEMBER))
    )
    ;; Check proposer reputation (unless it's the very first proposal bootstrapping)
    (asserts! (or (is-eq current-count u0) (>= (get reputation proposer-info) MIN_REPUTATION_TO_PROPOSE)) ERR_INSUFFICIENT_REPUTATION)

    (let
      (
        (new-proposal {
          proposer: tx-sender,
          title: title,
          description: description,
          start-block: start-height,
          end-block: end-height,
          votes-for: u0,
          votes-against: u0,
          status: "active",
          executed: false,
          transfer-amount: transfer-amount,
          transfer-to: transfer-to,
          add-member: add-member
        })
      )
      (map-set proposals current-count new-proposal)
      (var-set proposal-count (+ current-count u1))
      
      ;; EVENT: Proposal Created
      (print {
        event: "proposal-created",
        proposal-id: current-count,
        proposer: tx-sender,
        title: title,
        transfer-amount: transfer-amount
      })
      (ok current-count)
    )
  )
)

;; 2. Vote
;; Updated to use reputation as voting weight
(define-public (vote (proposal-id uint) (vote-for bool))
  (let
    (
      (proposal (unwrap! (map-get? proposals proposal-id) ERR_NOT_FOUND))
      (voter-info (unwrap! (map-get? members tx-sender) ERR_NOT_MEMBER))
      (voter-participation (map-get? votes { proposal-id: proposal-id, voter: tx-sender }))
    )
    ;; Checks
    (asserts! (is-eq (get status proposal) "active") ERR_PROPOSAL_NOT_ACTIVE)
    (asserts! (<= burn-block-height (get end-block proposal)) ERR_PROPOSAL_EXPIRED)
    (asserts! (is-none voter-participation) ERR_ALREADY_VOTED)
    
    (let
      (
        (weight (get reputation voter-info))
        (current-votes-for (get votes-for proposal))
        (current-votes-against (get votes-against proposal))
        (new-votes-for (if vote-for (+ current-votes-for weight) current-votes-for))
        (new-votes-against (if (not vote-for) (+ current-votes-against weight) current-votes-against))
      )
      ;; Update Proposal
      (map-set proposals proposal-id (merge proposal {
        votes-for: new-votes-for,
        votes-against: new-votes-against
      }))
      
      ;; Record Vote
      (map-set votes { proposal-id: proposal-id, voter: tx-sender } {
        outcome: vote-for,
        amount: weight
      })
      
      ;; EVENT: Vote Cast
      (print {
        event: "vote-cast",
        proposal-id: proposal-id,
        voter: tx-sender,
        outcome: vote-for,
        weight: weight
      })
      
      (ok true)
    )
  )
)

;; 3. Execute Proposal
;; Updated to handle treasury transfers and membership additions
(define-public (execute-proposal (proposal-id uint))
  (let
    (
      (proposal (unwrap! (map-get? proposals proposal-id) ERR_NOT_FOUND))
    )
    (asserts! (> burn-block-height (get end-block proposal)) ERR_EXECUTION_DELAY_NOT_MET)
    (asserts! (is-eq (get status proposal) "active") ERR_PROPOSAL_NOT_ACTIVE)
    
    (let
      (
        (passed (> (get votes-for proposal) (get votes-against proposal)))
        (new-status (if passed "passed" "failed"))
      )
      
      ;; Execution Logic
      (if passed
        (begin
           ;; Treasury Transfer
           (if (> (get transfer-amount proposal) u0)
              (match (get transfer-to proposal)
                recipient (match (transfer-from-vault (get transfer-amount proposal) recipient)
                           success true
                           error false)
                false) 
              false
           )
           
           ;; Add Member
           (match (get add-member proposal)
              new-member (map-set members new-member { reputation: u10, joined-at: burn-block-height })
              false
           )
        )
        false
      )

      (map-set proposals proposal-id (merge proposal {
        status: new-status,
        executed: passed
      }))
      
      ;; EVENT: Proposal Concluded
      (print {
        event: "proposal-concluded",
        proposal-id: proposal-id,
        status: new-status,
        votes-for: (get votes-for proposal),
        votes-against: (get votes-against proposal),
        executed: passed
      })
      
      (ok passed)
    )
  )
)

;; --- Treasury Functions ---

(define-public (deposit (amount uint))
  (stx-transfer? amount tx-sender (as-contract tx-sender))
)

;; --- Membership Functions ---

;; Bootstrap function (only useful for initialization/testing in this simplified version)
;; In production, initial members would be set in contract-init or via DAO bootstrapping event
(define-public (bootstrap-member (new-member principal) (reputation uint))
  (let
     ((existing-count (var-get general-counter))) ;; reusing counter as crude "initialized" check or just allow anyone
      ;; For safety, let's only allow this if map is empty-ish or restrict to deployer?
      ;; For this "template", we'll restrict to deployer to seed the DAO
      (begin
        (asserts! (is-eq tx-sender DEPLOYER_PRINCIPAL) ERR_UNAUTHORIZED)
        (map-set members new-member { reputation: reputation, joined-at: burn-block-height })
        (ok true)
      )
  )
)
;; Since we don't have contract-init param for deployer, let's assume tx-sender is deployer at init? 
;; Or just hardcode deployer.
;; Actually, better to just allow the first member to be self-registered or hardcode in define-map?
;; define-map is empty.
;; Let's add a public initialization function that can only be called once to adding the caller as the first member.

(define-data-var initialized bool false)

(define-public (initialize-dao)
  (begin
    (asserts! (not (var-get initialized)) ERR_ALREADY_EXISTS)
    (var-set initialized true)
    (map-set members tx-sender { reputation: u100, joined-at: burn-block-height })
    (ok true)
  )
)


;; --- Utility Functions (Counter Logic) ---

(define-public (increment)
  (let ((new-val (+ (var-get general-counter) u1)))
    (begin
      (var-set general-counter new-val)
      ;; EVENT: Counter Incremented
      (print {
        event: "counter-increment",
        caller: tx-sender,
        new-value: new-val
      })
      (ok new-val)
    )
  )
)

(define-public (decrement)
  (let ((current-val (var-get general-counter)))
    (begin
      (asserts! (> current-val u0) (err u109)) ;; Underflow protection
      (let ((new-val (- current-val u1)))
        (var-set general-counter new-val)
        ;; EVENT: Counter Decremented
        (print {
          event: "counter-decrement",
          caller: tx-sender,
          new-value: new-val
        })
        (ok new-val)
      )
    )
  )
)

;; --- Read-Only Functions ---

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

(define-read-only (get-signature-count) ;; Kept name for compatibility/checking, but maps to proposal-count
  (var-get proposal-count)
)

(define-read-only (get-general-counter)
  (var-get general-counter)
)

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

(define-read-only (get-member-info (member principal))
    (map-get? members member)
)

(define-read-only (get-treasury-balance)
    (stx-get-balance (as-contract tx-sender))
)

;; Private helper to transfer funds
(define-private (transfer-from-vault (amount uint) (recipient principal))
  (as-contract (stx-transfer? amount tx-sender recipient))
)

;; Private helper to define constants we might need

Functions (15)

FunctionAccessArgs
create-proposalpublictitle: (string-ascii 50
votepublicproposal-id: uint, vote-for: bool
execute-proposalpublicproposal-id: uint
depositpublicamount: uint
bootstrap-memberpublicnew-member: principal, reputation: uint
initialize-daopublic
incrementpublic
decrementpublic
get-proposalread-onlyproposal-id: uint
get-signature-countread-only
get-general-counterread-only
get-voteread-onlyproposal-id: uint, voter: principal
get-member-inforead-onlymember: principal
get-treasury-balanceread-only
transfer-from-vaultprivateamount: uint, recipient: principal