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 : 38 : (define-public (is-dao-or-extension)
61 [ - ][ + + ]: 55 : (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 : 38 : (define-public (add-proposal (proposal <proposal-trait>) (data {start-burn-height: uint, end-burn-height: uint, proposer: principal, custom-majority: (optional uint)}))
69 : 55 : (begin
70 : 55 : (try! (is-dao-or-extension))
71 [ - ]: 55 : (asserts! (is-none (contract-call? .bigmarket-dao executed-at proposal)) err-proposal-already-executed)
72 [ + ][ + + ]: 55 : (asserts! (match (get custom-majority data) majority (> majority u5000) true) err-not-majority)
73 : 53 : (print {event: "propose", proposal: proposal, proposer: tx-sender})
74 [ - ]: 53 : (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 : 7 : (define-read-only (get-proposal-data (proposal principal))
83 : 7 : (map-get? proposals proposal)
84 : : )
85 : :
86 : : ;; Votes
87 : :
88 : 33 : (define-read-only (get-current-total-votes (proposal principal) (voter principal))
89 : 55 : (default-to u0 (map-get? member-total-votes {proposal: proposal, voter: voter}))
90 : : )
91 : :
92 : 33 : (define-public (vote (amount uint) (for bool) (proposal principal) (reclaim-proposal (optional principal)))
93 : 55 : (process-vote-internal amount for proposal tx-sender reclaim-proposal)
94 : : )
95 : :
96 : 0 : (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 : 0 : (begin
105 : 0 : (ok (fold fold-vote votes u0))
106 : : )
107 : : )
108 : :
109 : 0 : (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 : 0 : (let
117 : : (
118 : 0 : (vote-result (process-vote input-vote))
119 : : )
120 : 0 : (if (unwrap! vote-result u0)
121 [ - ]: 0 : (+ current u1)
122 [ - ]: 0 : current)
123 : : )
124 : : )
125 : :
126 : 0 : (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 : 0 : (let
134 : : (
135 : : ;; Extract relevant fields from the message
136 : 0 : (message-data (get message input-vote))
137 : 0 : (proposal (get proposal message-data))
138 : 0 : (reclaim-proposal (get reclaim-proposal message-data))
139 : 0 : (voter (get voter message-data))
140 : 0 : (amount (get amount message-data))
141 : 0 : (for (get vote message-data))
142 : 0 : (structured-data-hash (sha256 (unwrap! (to-consensus-buff? message-data) err-unauthorised)))
143 : : ;; Verify the signature
144 : 0 : (is-valid-sig (verify-signed-structured-data structured-data-hash (get signature input-vote) voter))
145 : : )
146 : 0 : (if is-valid-sig
147 [ - ]: 0 : (process-vote-internal amount for proposal voter reclaim-proposal)
148 [ - ]: 0 : (begin
149 : 0 : (ok false) ;; Invalid signature, skip vote
150 : : )
151 : : )
152 : : )
153 : : )
154 : :
155 : 33 : (define-private (process-vote-internal (amount uint) (for bool) (proposal principal) (voter principal) (reclaim-proposal (optional principal)))
156 : 55 : (let
157 : : (
158 : 55 : (proposal-data (unwrap! (map-get? proposals proposal) err-unknown-proposal))
159 : : )
160 [ - + ]: 55 : (if (is-some reclaim-proposal) (try! (reclaim-votes reclaim-proposal)) true)
161 [ - ]: 55 : (asserts! (>= burn-block-height (get start-burn-height proposal-data)) err-proposal-inactive)
162 [ - ]: 55 : (asserts! (< burn-block-height (get end-burn-height proposal-data)) err-proposal-inactive)
163 : 55 : (map-set member-total-votes {proposal: proposal, voter: voter}
164 : 55 : (+ (get-current-total-votes proposal voter) amount)
165 : : )
166 : 55 : (map-set proposals proposal
167 : 55 : (if for
168 [ + ]: 50 : (merge proposal-data {votes-for: (+ (get votes-for proposal-data) amount)})
169 [ + ]: 5 : (merge proposal-data {votes-against: (+ (get votes-against proposal-data) amount)})
170 : : )
171 : : )
172 : 55 : (print {event: "vote", proposal: proposal, voter: voter, for: for, amount: amount})
173 : 55 : (contract-call? .bme000-0-governance-token bmg-lock amount voter)
174 : : )
175 : : )
176 : :
177 : : ;; Conclusion
178 : :
179 : 33 : (define-public (conclude (proposal <proposal-trait>))
180 : 50 : (let
181 : : (
182 : 50 : (proposal-data (unwrap! (map-get? proposals (contract-of proposal)) err-unknown-proposal))
183 : 0 : (passed
184 : 50 : (match (get custom-majority proposal-data)
185 [ + ]: 48 : 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 [ - ]: 50 : (asserts! (not (get concluded proposal-data)) err-proposal-already-concluded)
191 [ + ]: 50 : (asserts! (>= burn-block-height (get end-burn-height proposal-data)) err-end-block-height-not-reached)
192 : 49 : (map-set proposals (contract-of proposal) (merge proposal-data {concluded: true, passed: passed}))
193 : 49 : (print {event: "conclude", proposal: proposal, passed: passed})
194 : 49 : (try! (contract-call? .bme030-0-reputation-token mint tx-sender u11 u3))
195 [ + + ]: 49 : (and passed (try! (contract-call? .bigmarket-dao execute proposal tx-sender)))
196 : 47 : (ok passed)
197 : : )
198 : : )
199 : :
200 : : ;; Reclamation
201 : :
202 : 0 : (define-public (reclaim-votes (proposal (optional principal)))
203 : 0 : (let
204 : : (
205 : 0 : (reclaim-proposal (unwrap! proposal err-unknown-proposal))
206 : 0 : (proposal-data (unwrap! (map-get? proposals reclaim-proposal) err-unknown-proposal))
207 : 0 : (votes (unwrap! (map-get? member-total-votes {proposal: reclaim-proposal, voter: tx-sender}) err-no-votes-to-return))
208 : : )
209 [ - ]: 0 : (asserts! (get concluded proposal-data) err-proposal-not-concluded)
210 : 0 : (map-delete member-total-votes {proposal: reclaim-proposal, voter: tx-sender})
211 : 0 : (try! (contract-call? .bme030-0-reputation-token mint tx-sender u12 u2))
212 : 0 : (print {event: "reclaim-votes", proposal: reclaim-proposal, votes: votes, voter: tx-sender})
213 : 0 : (contract-call? .bme000-0-governance-token bmg-unlock votes tx-sender)
214 : : )
215 : : )
216 : :
217 : : ;; --- Extension callback
218 : :
219 : 0 : (define-public (callback (sender principal) (memo (buff 34)))
220 : 0 : (ok true)
221 : : )
222 : :
223 : 0 : (define-read-only (verify-signature (hash (buff 32)) (signature (buff 65)) (signer principal))
224 : 0 : (is-eq (principal-of? (unwrap! (secp256k1-recover? hash signature) false)) (ok signer))
225 : : )
226 : :
227 : 0 : (define-read-only (verify-signed-structured-data (structured-data-hash (buff 32)) (signature (buff 65)) (signer principal))
228 : 0 : (verify-signature (sha256 (concat structured-data-header structured-data-hash)) signature signer)
229 : : )
230 : :
231 : 0 : (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 : 0 : (let
241 : : (
242 : : ;; Compute the structured data hash
243 : 0 : (structured-data-hash (sha256 (unwrap! (to-consensus-buff? message-data) err-unauthorised)))
244 : : )
245 : : ;; Verify the signature using the computed hash
246 : 0 : (ok (verify-signed-structured-data structured-data-hash signature signer))
247 : : )
248 : : )
|