Source Code

;; Governance Contract
;; Decentralized governance for TimeLock Exchange
;; Enables proposal creation, voting, and execution

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-authorized (err u101))
(define-constant err-invalid-proposal (err u102))
(define-constant err-proposal-not-found (err u103))
(define-constant err-voting-closed (err u104))
(define-constant err-already-voted (err u105))
(define-constant err-insufficient-voting-power (err u106))
(define-constant err-proposal-not-ready (err u107))
(define-constant err-proposal-defeated (err u108))
(define-constant err-proposal-already-executed (err u109))
(define-constant err-quorum-not-met (err u110))
(define-constant err-invalid-duration (err u111))

;; Governance parameters
(define-data-var min-proposal-threshold uint u100000000) ;; 100 STX minimum to create proposal
(define-data-var voting-period uint u10080) ;; ~7 days in blocks
(define-data-var execution-delay uint u1440) ;; ~1 day delay after voting ends
(define-data-var quorum-percentage uint u4) ;; 4% of total supply needed
(define-data-var proposal-count uint u0)
(define-data-var total-voting-power uint u0)

;; Proposal states
(define-constant STATE_PENDING u0)
(define-constant STATE_ACTIVE u1)
(define-constant STATE_DEFEATED u2)
(define-constant STATE_SUCCEEDED u3)
(define-constant STATE_QUEUED u4)
(define-constant STATE_EXECUTED u5)
(define-constant STATE_CANCELLED u6)

;; Proposal types
(define-constant TYPE_PARAMETER_CHANGE u1)
(define-constant TYPE_CONTRACT_UPGRADE u2)
(define-constant TYPE_TREASURY_SPEND u3)
(define-constant TYPE_EMERGENCY_ACTION u4)

;; Proposals
(define-map proposals
  { proposal-id: uint }
  {
    proposer: principal,
    title: (string-ascii 128),
    description: (string-utf8 1024),
    proposal-type: uint,
    start-block: uint,
    end-block: uint,
    for-votes: uint,
    against-votes: uint,
    abstain-votes: uint,
    state: uint,
    execution-eta: uint,
    executed: bool,
    cancelled: bool,
    target-contract: (optional principal),
    call-data: (optional (buff 256))
  }
)

;; Vote records
(define-map votes
  { proposal-id: uint, voter: principal }
  {
    support: uint, ;; 0 = against, 1 = for, 2 = abstain
    voting-power: uint,
    block-height: uint
  }
)

;; Voting power (can be based on staked tokens or locked positions)
(define-map voting-power
  { address: principal }
  {
    power: uint,
    delegated-to: (optional principal),
    delegated-power: uint
  }
)

;; Delegation tracking
(define-map delegations
  { delegator: principal }
  { delegate: principal }
)

;; Proposal actions for execution
(define-map proposal-actions
  { proposal-id: uint, action-index: uint }
  {
    target: principal,
    value: uint,
    signature: (string-ascii 64),
    data: (buff 256)
  }
)

(define-map proposal-action-count
  { proposal-id: uint }
  { count: uint }
)

;; Events tracking
(define-map governance-events
  { event-id: uint }
  {
    event-type: (string-ascii 32),
    proposal-id: uint,
    actor: principal,
    block-height: uint,
    data: (optional (buff 128))
  }
)

(define-data-var event-count uint u0)

;; Read-only functions

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

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

;; Get voting power for address
(define-read-only (get-voting-power (address principal))
  (default-to { power: u0, delegated-to: none, delegated-power: u0 }
    (map-get? voting-power { address: address }))
)

;; Get effective voting power (including delegations)
(define-read-only (get-effective-voting-power (address principal))
  (let (
    (own-power (get power (get-voting-power address)))
    (delegated (get delegated-power (get-voting-power address)))
    (delegation (map-get? delegations { delegator: address }))
  )
    (if (is-some delegation)
      delegated ;; If delegated, only count delegated power received
      (+ own-power delegated)
    )
  )
)

;; Calculate proposal state
(define-read-only (get-proposal-state (proposal-id uint))
  (match (map-get? proposals { proposal-id: proposal-id })
    proposal
      (if (get cancelled proposal)
        STATE_CANCELLED
        (if (get executed proposal)
          STATE_EXECUTED
          (if (< stacks-block-height (get start-block proposal))
            STATE_PENDING
            (if (<= stacks-block-height (get end-block proposal))
              STATE_ACTIVE
              (if (proposal-succeeded proposal)
                (if (>= stacks-block-height (get execution-eta proposal))
                  STATE_QUEUED
                  STATE_SUCCEEDED
                )
                STATE_DEFEATED
              )
            )
          )
        )
      )
    STATE_PENDING
  )
)

;; Check if proposal succeeded
(define-read-only (proposal-succeeded (proposal {
  proposer: principal,
  title: (string-ascii 128),
  description: (string-utf8 1024),
  proposal-type: uint,
  start-block: uint,
  end-block: uint,
  for-votes: uint,
  against-votes: uint,
  abstain-votes: uint,
  state: uint,
  execution-eta: uint,
  executed: bool,
  cancelled: bool,
  target-contract: (optional principal),
  call-data: (optional (buff 256))
}))
  (let (
    (total-votes (+ (get for-votes proposal) (get against-votes proposal)))
    (quorum-needed (/ (* (var-get total-voting-power) (var-get quorum-percentage)) u100))
  )
    (and
      (> (get for-votes proposal) (get against-votes proposal))
      (>= total-votes quorum-needed)
    )
  )
)

;; Get proposal results
(define-read-only (get-proposal-results (proposal-id uint))
  (match (map-get? proposals { proposal-id: proposal-id })
    proposal
      (ok {
        for-votes: (get for-votes proposal),
        against-votes: (get against-votes proposal),
        abstain-votes: (get abstain-votes proposal),
        total-votes: (+ (+ (get for-votes proposal) (get against-votes proposal)) (get abstain-votes proposal)),
        quorum-reached: (>= (+ (get for-votes proposal) (get against-votes proposal)) 
          (/ (* (var-get total-voting-power) (var-get quorum-percentage)) u100)),
        passed: (proposal-succeeded proposal)
      })
    (err err-proposal-not-found)
  )
)

;; Check if address has voted
(define-read-only (has-voted (proposal-id uint) (voter principal))
  (is-some (map-get? votes { proposal-id: proposal-id, voter: voter }))
)

;; Get governance parameters
(define-read-only (get-governance-params)
  {
    min-proposal-threshold: (var-get min-proposal-threshold),
    voting-period: (var-get voting-period),
    execution-delay: (var-get execution-delay),
    quorum-percentage: (var-get quorum-percentage),
    total-voting-power: (var-get total-voting-power)
  }
)

;; Public functions

;; Register voting power (called by staking contract)
(define-public (register-voting-power (amount uint))
  (let (
    (current (get-voting-power tx-sender))
    (new-power (+ (get power current) amount))
  )
    (map-set voting-power
      { address: tx-sender }
      (merge current { power: new-power })
    )
    (var-set total-voting-power (+ (var-get total-voting-power) amount))
    (ok new-power)
  )
)

;; Remove voting power (called when unstaking)
(define-public (remove-voting-power (amount uint))
  (let (
    (current (get-voting-power tx-sender))
    (current-power (get power current))
  )
    (asserts! (>= current-power amount) err-insufficient-voting-power)
    (map-set voting-power
      { address: tx-sender }
      (merge current { power: (- current-power amount) })
    )
    (var-set total-voting-power (- (var-get total-voting-power) amount))
    (ok (- current-power amount))
  )
)

;; Delegate voting power
(define-public (delegate (delegate-to principal))
  (let (
    (delegator-power (get-voting-power tx-sender))
    (delegate-power (get-voting-power delegate-to))
  )
    ;; Set delegation
    (map-set delegations
      { delegator: tx-sender }
      { delegate: delegate-to }
    )
    
    ;; Update delegator
    (map-set voting-power
      { address: tx-sender }
      (merge delegator-power { delegated-to: (some delegate-to) })
    )
    
    ;; Update delegate's delegated power
    (map-set voting-power
      { address: delegate-to }
      (merge delegate-power { 
        delegated-power: (+ (get delegated-power delegate-power) (get power delegator-power))
      })
    )
    
    (ok delegate-to)
  )
)

;; Remove delegation
(define-public (undelegate)
  (let (
    (delegation (unwrap! (map-get? delegations { delegator: tx-sender }) err-not-authorized))
    (delegate-to (get delegate delegation))
    (delegator-power (get-voting-power tx-sender))
    (delegate-power (get-voting-power delegate-to))
  )
    ;; Remove delegation record
    (map-delete delegations { delegator: tx-sender })
    
    ;; Update delegator
    (map-set voting-power
      { address: tx-sender }
      (merge delegator-power { delegated-to: none })
    )
    
    ;; Update delegate's delegated power
    (map-set voting-power
      { address: delegate-to }
      (merge delegate-power {
        delegated-power: (- (get delegated-power delegate-power) (get power delegator-power))
      })
    )
    
    (ok true)
  )
)

;; Create proposal
(define-public (create-proposal 
  (title (string-ascii 128))
  (description (string-utf8 1024))
  (proposal-type uint)
  (target-contract (optional principal))
  (call-data (optional (buff 256)))
)
  (let (
    (proposer-power (get-effective-voting-power tx-sender))
    (proposal-id (+ (var-get proposal-count) u1))
    (start-block (+ stacks-block-height u1))
    (end-block (+ start-block (var-get voting-period)))
  )
    ;; Check voting power threshold
    (asserts! (>= proposer-power (var-get min-proposal-threshold)) err-insufficient-voting-power)
    ;; Validate proposal type
    (asserts! (and (>= proposal-type u1) (<= proposal-type u4)) err-invalid-proposal)
    
    ;; Create proposal
    (map-set proposals
      { proposal-id: proposal-id }
      {
        proposer: tx-sender,
        title: title,
        description: description,
        proposal-type: proposal-type,
        start-block: start-block,
        end-block: end-block,
        for-votes: u0,
        against-votes: u0,
        abstain-votes: u0,
        state: STATE_PENDING,
        execution-eta: (+ end-block (var-get execution-delay)),
        executed: false,
        cancelled: false,
        target-contract: target-contract,
        call-data: call-data
      }
    )
    
    ;; Update proposal count
    (var-set proposal-count proposal-id)
    
    ;; Record event
    (record-event "proposal-created" proposal-id tx-sender none)
    
    (ok proposal-id)
  )
)

;; Cast vote
(define-public (cast-vote (proposal-id uint) (support uint))
  (let (
    (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) err-proposal-not-found))
    (voter-power (get-effective-voting-power tx-sender))
  )
    ;; Check voting is active
    (asserts! (>= stacks-block-height (get start-block proposal)) err-voting-closed)
    (asserts! (<= stacks-block-height (get end-block proposal)) err-voting-closed)
    ;; Check not already voted
    (asserts! (not (has-voted proposal-id tx-sender)) err-already-voted)
    ;; Check has voting power
    (asserts! (> voter-power u0) err-insufficient-voting-power)
    ;; Validate support value
    (asserts! (<= support u2) err-invalid-proposal)
    
    ;; Record vote
    (map-set votes
      { proposal-id: proposal-id, voter: tx-sender }
      {
        support: support,
        voting-power: voter-power,
        block-height: stacks-block-height
      }
    )
    
    ;; Update proposal vote counts
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal
        (if (is-eq support u1)
          { for-votes: (+ (get for-votes proposal) voter-power), against-votes: (get against-votes proposal), abstain-votes: (get abstain-votes proposal) }
          (if (is-eq support u0)
            { for-votes: (get for-votes proposal), against-votes: (+ (get against-votes proposal) voter-power), abstain-votes: (get abstain-votes proposal) }
            { for-votes: (get for-votes proposal), against-votes: (get against-votes proposal), abstain-votes: (+ (get abstain-votes proposal) voter-power) }
          )
        )
      )
    )
    
    ;; Record event
    (record-event "vote-cast" proposal-id tx-sender none)
    
    (ok { proposal-id: proposal-id, support: support, power: voter-power })
  )
)

;; Queue proposal for execution
(define-public (queue-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) err-proposal-not-found))
    (state (get-proposal-state proposal-id))
  )
    ;; Must be succeeded
    (asserts! (is-eq state STATE_SUCCEEDED) err-proposal-not-ready)
    
    ;; Update state
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal { state: STATE_QUEUED })
    )
    
    ;; Record event
    (record-event "proposal-queued" proposal-id tx-sender none)
    
    (ok (get execution-eta proposal))
  )
)

;; Execute proposal
(define-public (execute-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) err-proposal-not-found))
    (state (get-proposal-state proposal-id))
  )
    ;; Must be queued and ready
    (asserts! (is-eq state STATE_QUEUED) err-proposal-not-ready)
    (asserts! (>= stacks-block-height (get execution-eta proposal)) err-proposal-not-ready)
    (asserts! (not (get executed proposal)) err-proposal-already-executed)
    
    ;; Mark as executed
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal { executed: true, state: STATE_EXECUTED })
    )
    
    ;; Record event
    (record-event "proposal-executed" proposal-id tx-sender none)
    
    ;; Note: Actual execution logic would call target contract
    ;; This is a simplified version
    
    (ok true)
  )
)

;; Cancel proposal (only proposer or admin)
(define-public (cancel-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) err-proposal-not-found))
  )
    ;; Only proposer or owner can cancel
    (asserts! (or (is-eq tx-sender (get proposer proposal)) (is-eq tx-sender contract-owner)) err-not-authorized)
    ;; Cannot cancel if executed
    (asserts! (not (get executed proposal)) err-proposal-already-executed)
    
    ;; Cancel
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal { cancelled: true, state: STATE_CANCELLED })
    )
    
    ;; Record event
    (record-event "proposal-cancelled" proposal-id tx-sender none)
    
    (ok true)
  )
)

;; Admin functions

;; Update voting period
(define-public (set-voting-period (new-period uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (>= new-period u1440) err-invalid-duration) ;; Minimum 1 day
    (var-set voting-period new-period)
    (ok new-period)
  )
)

;; Update quorum
(define-public (set-quorum (new-quorum uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (and (>= new-quorum u1) (<= new-quorum u50)) err-invalid-proposal) ;; 1-50%
    (var-set quorum-percentage new-quorum)
    (ok new-quorum)
  )
)

;; Update proposal threshold
(define-public (set-proposal-threshold (new-threshold uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set min-proposal-threshold new-threshold)
    (ok new-threshold)
  )
)

;; Private functions

;; Record governance event
(define-private (record-event 
  (event-type (string-ascii 32))
  (proposal-id uint)
  (actor principal)
  (data (optional (buff 128)))
)
  (let (
    (event-id (+ (var-get event-count) u1))
  )
    (map-set governance-events
      { event-id: event-id }
      {
        event-type: event-type,
        proposal-id: proposal-id,
        actor: actor,
        block-height: stacks-block-height,
        data: data
      }
    )
    (var-set event-count event-id)
    true
  )
)

Functions (21)

FunctionAccessArgs
get-proposalread-onlyproposal-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
get-voting-powerread-onlyaddress: principal
get-effective-voting-powerread-onlyaddress: principal
get-proposal-stateread-onlyproposal-id: uint
get-proposal-resultsread-onlyproposal-id: uint
has-votedread-onlyproposal-id: uint, voter: principal
get-governance-paramsread-only
register-voting-powerpublicamount: uint
remove-voting-powerpublicamount: uint
delegatepublicdelegate-to: principal
undelegatepublic
create-proposalpublictitle: (string-ascii 128
cast-votepublicproposal-id: uint, support: uint
queue-proposalpublicproposal-id: uint
execute-proposalpublicproposal-id: uint
cancel-proposalpublicproposal-id: uint
set-voting-periodpublicnew-period: uint
set-quorumpublicnew-quorum: uint
set-proposal-thresholdpublicnew-threshold: uint
record-eventprivateevent-type: (string-ascii 32