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 : : ;; )
|