LCOV - code coverage report
Current view: top level - contracts/extensions - bme001-0-proposal-voting.clar (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.2 % 55 54
Test Date: 2025-11-10 13:03:30 Functions: 100.0 % 9 9
Branches: 66.7 % 21 14

             Branch data     Line data    Source code
       1                 :             : ;; Title: BME01 Proposal Voting
       2                 :             : ;; Synopsis:
       3                 :             : ;; Allows governance token holders to vote on and conclude proposals.
       4                 :             : ;; Description:
       5                 :             : ;; Once proposals are submitted, they are open for voting after a lead up time
       6                 :             : ;; passes. Any token holder may vote on an open proposal, where one token equals
       7                 :             : ;; one vote. Members can vote until the voting period is over. After this period
       8                 :             : ;; anyone may trigger a conclusion. The proposal will then be executed if the
       9                 :             : ;; votes in favour exceed the ones against by the custom majority if set or simple majority
      10                 :             : ;; otherwise. Votes may additionally be submitted as batched list of signed structured 
      11                 :             : ;; voting messages using SIP-018.
      12                 :             : ;; The mechanism for voting requires Governance tokens to be burned in exchange for the 
      13                 :             : ;; equivalent number of lock tokens - these can be re-exchanged after the vote is concluded.
      14                 :             : 
      15                 :             : (impl-trait 'SP3JP0N1ZXGASRJ0F7QAHWFPGTVK9T2XNXDB908Z.extension-trait.extension-trait)
      16                 :             : (use-trait proposal-trait 'SP3JP0N1ZXGASRJ0F7QAHWFPGTVK9T2XNXDB908Z.proposal-trait.proposal-trait)
      17                 :             : 
      18                 :             : (define-constant err-unauthorised (err u3000))
      19                 :             : (define-constant err-proposal-already-executed (err u3002))
      20                 :             : (define-constant err-proposal-already-exists (err u3003))
      21                 :             : (define-constant err-unknown-proposal (err u3004))
      22                 :             : (define-constant err-proposal-already-concluded (err u3005))
      23                 :             : (define-constant err-proposal-inactive (err u3006))
      24                 :             : (define-constant err-proposal-not-concluded (err u3007))
      25                 :             : (define-constant err-no-votes-to-return (err u3008))
      26                 :             : (define-constant err-end-block-height-not-reached (err u3009))
      27                 :             : (define-constant err-disabled (err u3010))
      28                 :             : (define-constant err-not-majority (err u3011))
      29                 :             : 
      30                 :             : ;; (define-constant structured-data-prefix 0x534950303138)
      31                 :             : ;; (define-constant message-domain-hash (sha256 (unwrap! (to-consensus-buff?
      32                 :             : ;;      {
      33                 :             : ;;              name: "BigMarket",
      34                 :             : ;;              version: "1.0.0",
      35                 :             : ;;              chain-id: chain-id
      36                 :             : ;;      }
      37                 :             : ;;     ) err-unauthorised)
      38                 :             : ;; ))
      39                 :             : (define-constant custom-majority-upper u10000)
      40                 :             : ;; (define-constant structured-data-header (concat structured-data-prefix message-domain-hash))
      41                 :             : 
      42                 :             : (define-map proposals
      43                 :             :         principal
      44                 :             :         {
      45                 :             :                 custom-majority: (optional uint), ;; u10000 = 100%
      46                 :             :                 votes-for: uint,
      47                 :             :                 votes-against: uint,
      48                 :             :                 start-burn-height: uint,
      49                 :             :                 end-burn-height: uint,
      50                 :             :                 concluded: bool,
      51                 :             :                 passed: bool,
      52                 :             :                 proposer: principal
      53                 :             :         }
      54                 :             : )
      55                 :             : 
      56                 :             : (define-map member-total-votes {proposal: principal, voter: principal} uint)
      57                 :             : 
      58                 :             : ;; --- Authorisation check
      59                 :             : 
      60                 :         112 : (define-public (is-dao-or-extension)
      61    [ + ][ +  + ]:         145 :         (ok (asserts! (or (is-eq tx-sender .bigmarket-dao) (contract-call? .bigmarket-dao is-extension contract-caller)) err-unauthorised))
      62                 :             : )
      63                 :             : 
      64                 :             : ;; --- Internal DAO functions
      65                 :             : 
      66                 :             : ;; Proposals
      67                 :             : 
      68                 :         112 : (define-public (add-proposal (proposal <proposal-trait>) (data {start-burn-height: uint, end-burn-height: uint, proposer: principal, custom-majority: (optional uint)}))
      69                 :         145 :         (begin
      70                 :         145 :                 (try! (is-dao-or-extension))
      71            [ - ]:         143 :                 (asserts! (is-none (contract-call? .bigmarket-dao executed-at proposal)) err-proposal-already-executed)
      72    [ + ][ +  + ]:         143 :                 (asserts! (match (get custom-majority data) majority (> majority u5000) true) err-not-majority)
      73                 :         141 :                 (print {event: "propose", proposal: proposal, proposer: tx-sender})
      74            [ - ]:         141 :                 (ok (asserts! (map-insert proposals (contract-of proposal) (merge {votes-for: u0, votes-against: u0, concluded: false, passed: false} data)) err-proposal-already-exists))
      75                 :             :         )
      76                 :             : )
      77                 :             : 
      78                 :             : ;; --- Public functions
      79                 :             : 
      80                 :             : ;; Proposals
      81                 :             : 
      82                 :           9 : (define-read-only (get-proposal-data (proposal principal))
      83                 :           9 :         (map-get? proposals proposal)
      84                 :             : )
      85                 :             : 
      86                 :             : ;; Votes
      87                 :             : 
      88                 :         105 : (define-read-only (get-current-total-votes (proposal principal) (voter principal))
      89                 :         145 :         (default-to u0 (map-get? member-total-votes {proposal: proposal, voter: voter}))
      90                 :             : )
      91                 :             : 
      92                 :         105 : (define-public (vote (amount uint) (for bool) (proposal principal) (reclaim-proposal (optional principal)))
      93                 :         145 :   (process-vote-internal amount for proposal tx-sender reclaim-proposal)
      94                 :             : )
      95                 :             : 
      96                 :             : ;; (define-public (batch-vote (votes (list 50 {message: (tuple 
      97                 :             : ;;                                                 (attestation (string-ascii 100))
      98                 :             : ;;                                                 (proposal principal) 
      99                 :             : ;;                                                 (vote bool)
     100                 :             : ;;                                                 (voter principal)
     101                 :             : ;;                                                 (amount uint)
     102                 :             : ;;                                                 (reclaim-proposal (optional principal))), 
     103                 :             : ;;                                    signature: (buff 65)})))
     104                 :             : ;;   (begin
     105                 :             : ;;     (ok (fold fold-vote votes u0))
     106                 :             : ;;   )
     107                 :             : ;; )
     108                 :             : 
     109                 :             : ;; (define-private (fold-vote  (input-vote {message: (tuple 
     110                 :             : ;;                                                 (attestation (string-ascii 100)) 
     111                 :             : ;;                                                 (proposal principal) 
     112                 :             : ;;                                                 (vote bool)
     113                 :             : ;;                                                 (voter principal)
     114                 :             : ;;                                                 (amount uint) (reclaim-proposal (optional principal))), 
     115                 :             : ;;                                      signature: (buff 65)}) (current uint))
     116                 :             : ;;   (let
     117                 :             : ;;     (
     118                 :             : ;;       (vote-result (process-vote input-vote))
     119                 :             : ;;     )
     120                 :             : ;;      (if (unwrap! vote-result u0)
     121                 :             : ;;              (+ current u1)
     122                 :             : ;;              current)
     123                 :             : ;;   )
     124                 :             : ;; )
     125                 :             : 
     126                 :             : ;; (define-private (process-vote (input-vote {message: (tuple 
     127                 :             : ;;                                                 (attestation (string-ascii 100)) 
     128                 :             : ;;                                                 (proposal principal) 
     129                 :             : ;;                                                 (vote bool)
     130                 :             : ;;                                                 (voter principal)
     131                 :             : ;;                                                 (amount uint) (reclaim-proposal (optional principal))), 
     132                 :             : ;;                                      signature: (buff 65)}))
     133                 :             : ;;   (let
     134                 :             : ;;     (
     135                 :             : ;;       ;; Extract relevant fields from the message
     136                 :             : ;;              (message-data (get message input-vote))
     137                 :             : ;;              (proposal (get proposal message-data))
     138                 :             : ;;              (reclaim-proposal (get reclaim-proposal message-data))
     139                 :             : ;;              (voter (get voter message-data))
     140                 :             : ;;              (amount (get amount message-data))
     141                 :             : ;;              (for (get vote message-data))
     142                 :             : ;;              (structured-data-hash (sha256 (unwrap! (to-consensus-buff? message-data) err-unauthorised)))
     143                 :             : ;;              ;; Verify the signature
     144                 :             : ;;              (is-valid-sig (verify-signed-structured-data structured-data-hash (get signature input-vote) voter))
     145                 :             : ;;     )
     146                 :             : ;;     (if is-valid-sig
     147                 :             : ;;              (process-vote-internal amount for proposal voter reclaim-proposal)
     148                 :             : ;;              (begin 
     149                 :             : ;;                      (ok false) ;; Invalid signature, skip vote
     150                 :             : ;;              )
     151                 :             : ;;     )
     152                 :             : ;;   )
     153                 :             : ;; )
     154                 :             : 
     155                 :         105 : (define-private (process-vote-internal (amount uint) (for bool) (proposal principal) (voter principal) (reclaim-proposal (optional principal)))
     156                 :         145 :         (let
     157                 :             :                 (
     158                 :         145 :                         (proposal-data (unwrap! (map-get? proposals proposal) err-unknown-proposal))
     159                 :             :                 )
     160         [ -  + ]:         145 :                 (if (is-some reclaim-proposal) (try! (reclaim-votes reclaim-proposal)) true)
     161            [ - ]:         145 :                 (asserts! (>= burn-block-height (get start-burn-height proposal-data)) err-proposal-inactive)
     162            [ - ]:         145 :                 (asserts! (< burn-block-height (get end-burn-height proposal-data)) err-proposal-inactive)
     163                 :         145 :                 (map-set member-total-votes {proposal: proposal, voter: voter}
     164                 :         145 :                         (+ (get-current-total-votes proposal voter) amount)
     165                 :             :                 )
     166                 :         145 :                 (map-set proposals proposal
     167                 :         145 :                         (if for
     168            [ + ]:         138 :                                 (merge proposal-data {votes-for: (+ (get votes-for proposal-data) amount)})
     169            [ + ]:           7 :                                 (merge proposal-data {votes-against: (+ (get votes-against proposal-data) amount)})
     170                 :             :                         )
     171                 :             :                 )
     172                 :         145 :                 (print {event: "vote", proposal: proposal, voter: voter, for: for, amount: amount})
     173                 :         145 :                 (contract-call? .bme000-0-governance-token bmg-lock amount voter)
     174                 :             :         )
     175                 :             : )
     176                 :             : 
     177                 :             : ;; Conclusion
     178                 :             : 
     179                 :         105 : (define-public (conclude (proposal <proposal-trait>))
     180                 :         138 :         (let
     181                 :             :                 (
     182                 :         138 :                         (proposal-data (unwrap! (map-get? proposals (contract-of proposal)) err-unknown-proposal))
     183                 :           0 :                         (passed
     184                 :         138 :                                 (match (get custom-majority proposal-data)
     185            [ + ]:         136 :                                         majority (> (* (get votes-for proposal-data) custom-majority-upper) (* (+ (get votes-for proposal-data) (get votes-against proposal-data)) majority))
     186            [ + ]:           2 :                                         (> (get votes-for proposal-data) (get votes-against proposal-data))
     187                 :             :                                 )
     188                 :             :                         )
     189                 :             :                 )
     190            [ - ]:         138 :                 (asserts! (not (get concluded proposal-data)) err-proposal-already-concluded)
     191            [ + ]:         138 :                 (asserts! (>= burn-block-height (get end-burn-height proposal-data)) err-end-block-height-not-reached)
     192                 :         137 :                 (map-set proposals (contract-of proposal) (merge proposal-data {concluded: true, passed: passed}))
     193                 :         137 :                 (print {event: "conclude", proposal: proposal, passed: passed})
     194                 :         137 :         (try! (contract-call? .bme030-0-reputation-token mint tx-sender u11 u3))
     195         [ +  + ]:         137 :                 (and passed (try! (contract-call? .bigmarket-dao execute proposal tx-sender)))
     196                 :         124 :                 (ok passed)
     197                 :             :         )
     198                 :             : )
     199                 :             : 
     200                 :             : ;; Reclamation
     201                 :             : 
     202                 :           1 : (define-public (reclaim-votes (proposal (optional principal)))
     203                 :           1 :         (let
     204                 :             :                 (
     205                 :           1 :                         (reclaim-proposal (unwrap! proposal err-unknown-proposal))
     206                 :           1 :                         (proposal-data (unwrap! (map-get? proposals reclaim-proposal) err-unknown-proposal))
     207                 :           1 :                         (votes (unwrap! (map-get? member-total-votes {proposal: reclaim-proposal, voter: tx-sender}) err-no-votes-to-return))
     208                 :             :                 )
     209            [ - ]:           1 :                 (asserts! (get concluded proposal-data) err-proposal-not-concluded)
     210                 :           1 :                 (map-delete member-total-votes {proposal: reclaim-proposal, voter: tx-sender})
     211                 :           1 :         (try! (contract-call? .bme030-0-reputation-token mint tx-sender u12 u2))
     212                 :           1 :                 (print {event: "reclaim-votes", proposal: reclaim-proposal, votes: votes, voter: tx-sender})
     213                 :           1 :                 (contract-call? .bme000-0-governance-token bmg-unlock votes tx-sender)
     214                 :             :         )
     215                 :             : )
     216                 :             : 
     217                 :             : ;; --- Extension callback
     218                 :             : 
     219                 :           2 : (define-public (callback (sender principal) (memo (buff 34)))
     220                 :           2 :         (ok true)
     221                 :             : )
     222                 :             : 
     223                 :             : ;; (define-read-only (verify-signature (hash (buff 32)) (signature (buff 65)) (signer principal))
     224                 :             : ;;      (is-eq (principal-of? (unwrap! (secp256k1-recover? hash signature) false)) (ok signer))
     225                 :             : ;; )
     226                 :             : 
     227                 :             : ;; (define-read-only (verify-signed-structured-data (structured-data-hash (buff 32)) (signature (buff 65)) (signer principal))
     228                 :             : ;;      (verify-signature (sha256 (concat structured-data-header structured-data-hash)) signature signer)
     229                 :             : ;; )
     230                 :             : 
     231                 :             : ;; (define-read-only (verify-signed-tuple
     232                 :             : ;;     (message-data (tuple 
     233                 :             : ;;                     (attestation (string-ascii 100))
     234                 :             : ;;                     (proposal principal)
     235                 :             : ;;                     (vote bool)
     236                 :             : ;;                     (voter principal)
     237                 :             : ;;                     (amount uint)))
     238                 :             : ;;     (signature (buff 65))
     239                 :             : ;;     (signer principal))
     240                 :             : ;;   (let
     241                 :             : ;;     (
     242                 :             : ;;       ;; Compute the structured data hash
     243                 :             : ;;              (structured-data-hash (sha256 (unwrap! (to-consensus-buff? message-data) err-unauthorised)))
     244                 :             : ;;     )
     245                 :             : ;;     ;; Verify the signature using the computed hash
     246                 :             : ;;     (ok (verify-signed-structured-data structured-data-hash signature signer))
     247                 :             : ;;   )
     248                 :             : ;; )
        

Generated by: LCOV version 2.3.2-1