Skip to content

Commit

Permalink
feat: add voting
Browse files Browse the repository at this point in the history
  • Loading branch information
friedger committed Jul 15, 2024
1 parent dcb97b6 commit 3372bc4
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 7 deletions.
241 changes: 237 additions & 4 deletions contracts/proposals/ccip019-pox-4-stacking.clar
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,56 @@

;; ERRORS

(define-constant ERR_PANIC (err u1400))

(define-constant ERR_PANIC (err u19000))
(define-constant ERR_SAVING_VOTE (err u19001))
(define-constant ERR_VOTED_ALREADY (err u19002))
(define-constant ERR_NOTHING_STACKED (err u19003))
(define-constant ERR_USER_NOT_FOUND (err u19004))
(define-constant ERR_PROPOSAL_NOT_ACTIVE (err u19005))
(define-constant ERR_PROPOSAL_STILL_ACTIVE (err u19006))
(define-constant ERR_VOTE_FAILED (err u19007))
;; CONSTANTS

(define-constant SELF (as-contract tx-sender))
(define-constant CCIP_019 {
name: "PoX-4 Stacking",
link: "",
link: "https://github.com/citycoins/protocol/pull/67",
hash: "",
})

(define-constant VOTE_SCALE_FACTOR (pow u10 u16)) ;; 16 decimal places

(define-constant MIA_ID (default-to u2 (contract-call? .ccd004-city-registry get-city-id "mia")))

;; DATA VARS

;; vote block heights
(define-data-var voteActive bool true)
(define-data-var voteStart uint u0)
(define-data-var voteEnd uint u0)

;; start the vote when deployed
(var-set voteStart block-height)

;; DATA MAPS

(define-map CityVotes
uint ;; city ID
{ ;; vote
totalAmountYes: uint,
totalAmountNo: uint,
totalVotesYes: uint,
totalVotesNo: uint,
}
)

(define-map UserVotes
uint ;; user ID
{ ;; vote
vote: bool,
mia: uint,
}
)

;; PUBLIC FUNCTIONS

Expand All @@ -25,6 +64,11 @@
(miaBalance (contract-call? .ccd002-treasury-mia-mining-v2 get-balance-stx))
)

(try! (is-executable))
;; update vote variables
(var-set voteEnd block-height)
(var-set voteActive false)

;; enable new treasuries in the DAO
(try! (contract-call? .base-dao set-extensions
(list
Expand Down Expand Up @@ -52,8 +96,197 @@
)
)


(define-public (vote-on-proposal (vote bool))
(let
(
(voterId (unwrap! (contract-call? .ccd003-user-registry get-user-id contract-caller) ERR_USER_NOT_FOUND))
(voterRecord (map-get? UserVotes voterId))
)
;; check if vote is active
(asserts! (var-get voteActive) ERR_PROPOSAL_NOT_ACTIVE)
;; check if vote record exists for user
(match voterRecord record
;; if the voterRecord exists
(let
(
(oldVote (get vote record))
(miaVoteAmount (get mia record))
)
;; check vote is not the same as before
(asserts! (not (is-eq oldVote vote)) ERR_VOTED_ALREADY)
;; record the new vote for the user
(map-set UserVotes voterId
(merge record { vote: vote })
)
;; update vote stats for each city
(update-city-votes MIA_ID miaVoteAmount vote true)
(ok true)
)
;; if the voterRecord does not exist
(let
(
(miaVoteAmount (scale-down (default-to u0 (get-mia-vote voterId true))))
)
;; check that the user has a positive vote
(asserts! (or (> miaVoteAmount u0)) ERR_NOTHING_STACKED)
;; insert new user vote record
(asserts! (map-insert UserVotes voterId {
vote: vote,
mia: miaVoteAmount
}) ERR_SAVING_VOTE)
;; update vote stats for each city
(update-city-votes MIA_ID miaVoteAmount vote false)
(ok true)
)
)
)
)

;; READ ONLY FUNCTIONS

(define-read-only (get-proposal-info)
(some CCIP_019)
)
)


(define-read-only (is-executable)
(let
(
(votingRecord (unwrap! (get-vote-totals) ERR_PANIC))
(miaRecord (get mia votingRecord))
(voteTotals (get totals votingRecord))
)
;; check that there is at least one vote
(asserts! (or (> (get totalVotesYes voteTotals) u0) (> (get totalVotesNo voteTotals) u0)) ERR_VOTE_FAILED)
;; check that the yes total is more than no total
(asserts! (> (get totalVotesYes voteTotals) (get totalVotesNo voteTotals)) ERR_VOTE_FAILED)
;; check the "yes" votes are at least 25% of the total
(asserts! (>= (get totalAmountYes miaRecord) (/ (get totalAmountYes voteTotals) u4)) ERR_VOTE_FAILED)
;; allow execution
(ok true)
)
)

(define-read-only (is-vote-active)
(some (var-get voteActive))
)

(define-read-only (get-vote-period)
(if (and
(> (var-get voteStart) u0)
(> (var-get voteEnd) u0))
;; if both are set, return values
(some {
startBlock: (var-get voteStart),
endBlock: (var-get voteEnd),
length: (- (var-get voteEnd) (var-get voteStart))
})
;; else return none
none
)
)

(define-read-only (get-vote-total-mia)
(map-get? CityVotes MIA_ID)
)

(define-read-only (get-vote-total-mia-or-default)
(default-to { totalAmountYes: u0, totalAmountNo: u0, totalVotesYes: u0, totalVotesNo: u0 } (get-vote-total-mia))
)

(define-read-only (get-vote-totals)
(let
(
(miaRecord (get-vote-total-mia-or-default))
)
(some {
mia: miaRecord,
totals: {
totalAmountYes: (get totalAmountYes miaRecord),
totalAmountNo: (get totalAmountNo miaRecord),
totalVotesYes: (get totalVotesYes miaRecord),
totalVotesNo: (get totalVotesNo miaRecord),
}
})
)
)

(define-read-only (get-voter-info (id uint))
(map-get? UserVotes id)
)

;; mia vote calculation
;; returns (some uint) or (none)
;; optionally scaled by VOTE_SCALE_FACTOR (10^6)
(define-read-only (get-mia-vote (userId uint) (scaled bool))
(let
(
;; MAINNET: mia cycle 82 / first block BTC 838,250 STX 145,643
;; cycle 2 / u4500 used in tests
(cycle82Hash (unwrap! (get-block-hash u4500) none))
(cycle82Data (at-block cycle82Hash (contract-call? .ccd007-citycoin-stacking get-stacker MIA_ID u2 userId)))
(cycle82Amount (get stacked cycle82Data))
;; MAINNET: mia cycle 83 / first block BTC 840,350 STX 147,282
;; cycle 3 / u6600 used in tests
(cycle83Hash (unwrap! (get-block-hash u6600) none))
(cycle83Data (at-block cycle83Hash (contract-call? .ccd007-citycoin-stacking get-stacker MIA_ID u3 userId)))
(cycle83Amount (get stacked cycle83Data))
;; mia vote calculation
(scaledVote (/ (+ (scale-up cycle82Amount) (scale-up cycle83Amount)) u2))
)
;; check that at least one value is positive
(asserts! (or (> cycle82Amount u0) (> cycle83Amount u0)) none)
;; return scaled or unscaled value
(if scaled (some scaledVote) (some (/ scaledVote VOTE_SCALE_FACTOR)))
)
)

;; PRIVATE FUNCTIONS

;; update city vote map
(define-private (update-city-votes (cityId uint) (voteAmount uint) (vote bool) (changedVote bool))
(let
(
(cityRecord (default-to
{ totalAmountYes: u0, totalAmountNo: u0, totalVotesYes: u0, totalVotesNo: u0 }
(map-get? CityVotes cityId)))
)
;; do not record if amount is 0
(if (> voteAmount u0)
;; handle vote
(if vote
;; handle yes vote
(map-set CityVotes cityId {
totalAmountYes: (+ voteAmount (get totalAmountYes cityRecord)),
totalVotesYes: (+ u1 (get totalVotesYes cityRecord)),
totalAmountNo: (if changedVote (- (get totalAmountNo cityRecord) voteAmount) (get totalAmountNo cityRecord)),
totalVotesNo: (if changedVote (- (get totalVotesNo cityRecord) u1) (get totalVotesNo cityRecord))
})
;; handle no vote
(map-set CityVotes cityId {
totalAmountYes: (if changedVote (- (get totalAmountYes cityRecord) voteAmount) (get totalAmountYes cityRecord)),
totalVotesYes: (if changedVote (- (get totalVotesYes cityRecord) u1) (get totalVotesYes cityRecord)),
totalAmountNo: (+ voteAmount (get totalAmountNo cityRecord)),
totalVotesNo: (+ u1 (get totalVotesNo cityRecord)),
})
)
;; ignore calls with vote amount equal to 0
false)
)
)

;; get block hash by height
(define-private (get-block-hash (blockHeight uint))
(get-block-info? id-header-hash blockHeight)
)

;; CREDIT: ALEX math-fixed-point-16.clar

(define-private (scale-up (a uint))
(* a VOTE_SCALE_FACTOR)
)

(define-private (scale-down (a uint))
(/ a VOTE_SCALE_FACTOR)
)
3 changes: 0 additions & 3 deletions contracts/traits/ccip019-proxy-trait.clar

This file was deleted.

0 comments on commit 3372bc4

Please sign in to comment.