Source Code

;; proposal-submission.clar
;; Create and validate governance proposals

;; Error codes
(define-constant ERR-PROPOSAL-NOT-FOUND (err u5001))
(define-constant ERR-PROPOSAL-ALREADY-EXISTS (err u5002))
(define-constant ERR-INVALID-PROPOSAL (err u5003))
(define-constant ERR-MINIMUM-POWER-NOT-MET (err u5004))

;; Proposal status constants
(define-constant STATUS-PENDING u1)
(define-constant STATUS-ACTIVE u2)
(define-constant STATUS-EXPIRED u6)

;; Configuration
(define-constant MINIMUM-PROPOSAL-POWER u1000000) ;; 1 STX minimum to propose
(define-constant PROPOSAL-DURATION u1440) ;; ~10 days in blocks
(define-constant VOTING-DELAY u144) ;; ~1 day delay before voting starts

;; Data vars
(define-data-var proposal-count uint u0)

;; Data maps
(define-map proposals
  uint  ;; proposal-id
  {
    proposer: principal,
    title: (string-ascii 128),
    description: (string-utf8 1024),
    proposal-contract: (optional principal),
    created-at: uint,
    start-block: uint,
    end-block: uint,
    status: uint,
    execution-delay: uint
  }
)

(define-map proposal-by-contract principal uint)

;; Read-only functions

(define-read-only (get-proposal-count)
  (var-get proposal-count)
)

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

(define-read-only (get-proposal-by-contract (proposal-contract principal))
  (match (map-get? proposal-by-contract proposal-contract)
    id (get-proposal id)
    none
  )
)

(define-read-only (get-proposal-status (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (some (get status proposal))
    none
  )
)

(define-read-only (is-proposal-active (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (and 
      (is-eq (get status proposal) STATUS-ACTIVE)
      (>= stacks-block-height (get start-block proposal))
      (<= stacks-block-height (get end-block proposal))
    )
    false
  )
)

(define-read-only (can-propose (who principal))
  (>= (stx-get-balance who) MINIMUM-PROPOSAL-POWER)
)

;; Input checks (satisfy check_checker)
(define-private (check-ascii-128 (s (string-ascii 128)))
  (ok (asserts! (> (len s) u0) ERR-INVALID-PROPOSAL))
)

(define-private (check-utf8-1024 (s (string-utf8 1024)))
  (ok (asserts! (> (len s) u0) ERR-INVALID-PROPOSAL))
)

(define-private (check-optional-principal (p (optional principal)))
  (ok (asserts! (is-eq p p) ERR-INVALID-PROPOSAL))
)

(define-private (check-uint (n uint))
  (ok (asserts! (>= n u0) ERR-INVALID-PROPOSAL))
)

;; Authorization
(define-private (is-dao-or-extension)
  (contract-call? .dao-core-v2-c4 is-dao-or-extension)
)

;; Public functions

;; Create a new proposal
(define-public (propose 
  (title (string-ascii 128))
  (description (string-utf8 1024))
  (proposal-contract (optional principal))
)
  (let
    (
      (proposer-power (stx-get-balance tx-sender))
      (new-id (+ (var-get proposal-count) u1))
      (start-block (+ stacks-block-height VOTING-DELAY))
      (end-block (+ start-block PROPOSAL-DURATION))
    )
    (try! (check-ascii-128 title))
    (try! (check-utf8-1024 description))
    (try! (check-optional-principal proposal-contract))
    ;; Check minimum power requirement
    (asserts! (>= proposer-power MINIMUM-PROPOSAL-POWER) ERR-MINIMUM-POWER-NOT-MET)
    
    ;; Check proposal contract doesn't already have a proposal
    (match proposal-contract
      contract (asserts! (is-none (map-get? proposal-by-contract contract)) ERR-PROPOSAL-ALREADY-EXISTS)
      true
    )
    
    ;; Create proposal
    (map-set proposals new-id {
      proposer: tx-sender,
      title: title,
      description: description,
      proposal-contract: proposal-contract,
      created-at: stacks-block-height,
      start-block: start-block,
      end-block: end-block,
      status: STATUS-PENDING,
      execution-delay: u0
    })
    
    ;; Index by contract if provided
    (match proposal-contract
      contract (map-set proposal-by-contract contract new-id)
      true
    )
    
    (var-set proposal-count new-id)
    
    (print {
      event: "proposal-created",
      proposal-id: new-id,
      proposer: tx-sender,
      title: title,
      start-block: start-block,
      end-block: end-block
    })
    (ok new-id)
  )
)

;; Activate a pending proposal (moves from pending to active)
(define-public (activate-proposal (proposal-id uint))
  (begin
    (try! (check-uint proposal-id))
    (let
      (
        (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
      )
      (asserts! (is-eq (get status proposal) STATUS-PENDING) ERR-INVALID-PROPOSAL)
      (asserts! (>= stacks-block-height (get start-block proposal)) ERR-INVALID-PROPOSAL)
      
      (map-set proposals proposal-id (merge proposal {status: STATUS-ACTIVE}))
      
      (print {event: "proposal-activated", proposal-id: proposal-id})
      (ok true)
    )
  )
)

;; Update proposal status (called by voting contract)
(define-public (set-proposal-status (proposal-id uint) (new-status uint))
  (begin
    (try! (is-dao-or-extension))
    (try! (check-uint proposal-id))
    (asserts! (and (>= new-status STATUS-PENDING) (<= new-status STATUS-EXPIRED)) ERR-INVALID-PROPOSAL)
    (let
      (
        (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
      )
      (map-set proposals proposal-id (merge proposal {status: new-status}))
      (print {event: "proposal-status-updated", proposal-id: proposal-id, status: new-status})
      (ok true)
    )
  )
)

;; Mark proposal as expired
(define-public (expire-proposal (proposal-id uint))
  (begin
    (try! (check-uint proposal-id))
    (let
      (
        (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
      )
      (asserts! (> stacks-block-height (get end-block proposal)) ERR-INVALID-PROPOSAL)
      (asserts! (or (is-eq (get status proposal) STATUS-PENDING) (is-eq (get status proposal) STATUS-ACTIVE)) ERR-INVALID-PROPOSAL)
      
      (map-set proposals proposal-id (merge proposal {status: STATUS-EXPIRED}))
      
      (print {event: "proposal-expired", proposal-id: proposal-id})
      (ok true)
    )
  )
)

;; Extension callback
(define-public (callback (sender principal) (memo (buff 34)))
  (begin
    sender
    memo
    (ok true)
  )
)

Functions (16)

FunctionAccessArgs
get-proposal-countread-only
get-proposalread-onlyproposal-id: uint
get-proposal-by-contractread-onlyproposal-contract: principal
get-proposal-statusread-onlyproposal-id: uint
is-proposal-activeread-onlyproposal-id: uint
can-proposeread-onlywho: principal
check-ascii-128privates: (string-ascii 128
check-utf8-1024privates: (string-utf8 1024
check-optional-principalprivatep: (optional principal
check-uintprivaten: uint
is-dao-or-extensionprivate
proposepublictitle: (string-ascii 128
activate-proposalpublicproposal-id: uint
set-proposal-statuspublicproposal-id: uint, new-status: uint
expire-proposalpublicproposal-id: uint
callbackpublicsender: principal, memo: (buff 34