Source Code


;; nova-dao-proposals.clar
;; Standard proposal creation and voting mechanism for Nova Protocol.
;; CLARITY VERSION: 2

(use-trait nova-trait-governance .nova-trait-governance.nova-trait-governance)

(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-PROPOSAL-NOT-FOUND (err u101))
(define-constant ERR-ALREADY-VOTED (err u102))
(define-constant ERR-VOTING-CLOSED (err u103))
(define-constant ERR-INSUFFICIENT-VOTES (err u104))

(define-map proposals
    uint
    {
        proposer: principal,
        title: (string-ascii 50),
        description: (string-utf8 256),
        start-block: uint,
        end-block: uint,
        yes-votes: uint,
        no-votes: uint,
        executed: bool
    }
)

(define-map votes
    {proposal-id: uint, voter: principal}
    bool
)

(define-data-var proposal-count uint u0)
(define-data-var voting-delay uint u144) ;; ~1 day
(define-data-var voting-period uint u1008) ;; ~1 week

(define-public (create-proposal (title (string-ascii 50)) (description (string-utf8 256)) (token-trait <nova-trait-governance>))
    (let (
        (id (var-get proposal-count))
        (start (+ block-height (var-get voting-delay)))
        (end (+ start (var-get voting-period)))
        (threshold u100) ;; Min tokens to propose
    )
    ;; Check proposer balance at current block (or previous) to prevent spam
    (asserts! (>= (unwrap-panic (contract-call? token-trait get-balance tx-sender)) threshold) ERR-NOT-AUTHORIZED)

    (map-set proposals id {
        proposer: tx-sender,
        title: title,
        description: description,
        start-block: start,
        end-block: end,
        yes-votes: u0,
        no-votes: u0,
        executed: false
    })
    (var-set proposal-count (+ id u1))
    (ok id))
)

(define-public (vote (proposal-id uint) (vote-for bool) (token-trait <nova-trait-governance>))
    (let (
        (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
        (voter tx-sender)
        (has-voted (default-to false (map-get? votes {proposal-id: proposal-id, voter: voter})))
    )
    (asserts! (not has-voted) ERR-ALREADY-VOTED)
    (asserts! (<= block-height (get end-block proposal)) ERR-VOTING-CLOSED)
    (asserts! (>= block-height (get start-block proposal)) (err u105))

    (let (
        ;; Get voting power at start-block of proposal to prevent flash loan attacks
        (voting-power (unwrap-panic (contract-call? token-trait get-balance-at-block voter (get start-block proposal))))
    )
        (asserts! (> voting-power u0) ERR-NOT-AUTHORIZED)
        
        (map-set proposals proposal-id
            (merge proposal {
                yes-votes: (if vote-for (+ (get yes-votes proposal) voting-power) (get yes-votes proposal)),
                no-votes: (if vote-for (get no-votes proposal) (+ (get no-votes proposal) voting-power))
            })
        )
        (map-set votes {proposal-id: proposal-id, voter: voter} true)
        (ok voting-power)
    )
    )
)

(define-public (execute (proposal-id uint))
    (let (
        (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
    )
    (asserts! (> block-height (get end-block proposal)) (err u106))
    (asserts! (not (get executed proposal)) (err u107))
    (asserts! (> (get yes-votes proposal) (get no-votes proposal)) ERR-INSUFFICIENT-VOTES)

    ;; Execute logic would go here (e.g., contract-call to target)
    ;; For this example, just mark as executed.
    
    (map-set proposals proposal-id (merge proposal { executed: true }))
    (ok true))
)

Functions (3)

FunctionAccessArgs
create-proposalpublictitle: (string-ascii 50
votepublicproposal-id: uint, vote-for: bool, token-trait: <nova-trait-governance>
executepublicproposal-id: uint