Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

committing flexible funded proposal #17

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions contracts/extensions/ede007-snapshot-proposal-voting-v5.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
;; Title: EDE007 Snapshot Proposal Voting
;; Author: Marvin Janssen
;; Depends-On:
;; Synopsis:
;; This extension is an EcosystemDAO concept that allows all STX holders to
;; vote on proposals based on their STX balance.
;; Description:
;; This extension allows anyone with STX to vote on proposals. The maximum upper
;; bound, or voting power, depends on the amount of STX tokens the tx-sender
;; owned at the start block height of the proposal. The name "snapshot" comes
;; from the fact that the extension effectively uses the STX balance sheet
;; at a specific block heights to determine voting power.
;; Custom majority thresholds for voting are also possible on a per proposal basis.
;; A custom majority of 66% mean the percent of votes for must be greater than 66 for
;; the vote to carry.

(impl-trait .extension-trait.extension-trait)
(use-trait proposal-trait .proposal-trait.proposal-trait)

(define-constant err-unauthorised (err u3000))
(define-constant err-proposal-already-executed (err u3001))
(define-constant err-proposal-already-exists (err u3002))
(define-constant err-unknown-proposal (err u3003))
(define-constant err-proposal-already-concluded (err u3004))
(define-constant err-proposal-inactive (err u3005))
(define-constant err-insufficient-voting-capacity (err u3006))
(define-constant err-end-block-height-not-reached (err u3007))
(define-constant err-not-majority (err u3008))
(define-constant err-exceeds-voting-cap (err u3009))

(define-constant custom-majority-upper u10000)
(define-constant vote-cap u140000000000)

(define-map proposals
principal
{
votes-for: uint,
votes-against: uint,
start-block-height: uint,
end-block-height: uint,
concluded: bool,
passed: bool,
custom-majority: (optional uint), ;; u10000 = 100%
proposer: principal
}
)

(define-map member-total-votes {proposal: principal, voter: principal} uint)

;; --- Authorisation check

(define-public (is-dao-or-extension)
(ok (asserts! (or (is-eq tx-sender .executor-dao) (contract-call? .executor-dao is-extension contract-caller)) err-unauthorised))
)

;; --- Internal DAO functions

;; Proposals

(define-public (add-proposal (proposal <proposal-trait>) (data {start-block-height: uint, end-block-height: uint, proposer: principal, custom-majority: (optional uint)}))
(begin
(try! (is-dao-or-extension))
(asserts! (is-none (contract-call? .executor-dao executed-at proposal)) err-proposal-already-executed)
(asserts! (match (get custom-majority data) majority (> majority u5000) true) err-not-majority)
(print {event: "propose", proposal: proposal, proposer: tx-sender})
(ok (asserts! (map-insert proposals (contract-of proposal) (merge {votes-for: u0, votes-against: u0, concluded: false, passed: false} data)) err-proposal-already-exists))
)
)

;; --- Public functions

;; Proposals

(define-read-only (get-proposal-data (proposal principal))
(map-get? proposals proposal)
)

;; Votes

(define-read-only (get-current-total-votes (proposal principal) (voter principal))
(default-to u0 (map-get? member-total-votes {proposal: proposal, voter: voter}))
)

(define-read-only (get-historical-values (height uint) (who principal))
(at-block (unwrap! (get-block-info? id-header-hash height) none)
(some
{
user-balance: (stx-get-balance who),
voting-cap: vote-cap,
;;voting-cap: (contract-call? 'SP000000000000000000002Q6VF78.pox get-stacking-minimum)
}
)
)
)

(define-public (vote (amount uint) (for bool) (proposal principal))
(let
(
(proposal-data (unwrap! (map-get? proposals proposal) err-unknown-proposal))
(new-total-votes (+ (get-current-total-votes proposal tx-sender) amount))
(historical-values (unwrap! (get-historical-values (get start-block-height proposal-data) tx-sender) err-proposal-inactive))
)
(asserts! (>= block-height (get start-block-height proposal-data)) err-proposal-inactive)
(asserts! (< block-height (get end-block-height proposal-data)) err-proposal-inactive)
(asserts!
(<= new-total-votes (get user-balance historical-values))
err-insufficient-voting-capacity
)
(asserts!
(< new-total-votes (get voting-cap historical-values))
err-exceeds-voting-cap)

(map-set member-total-votes {proposal: proposal, voter: tx-sender} new-total-votes)
(map-set proposals proposal
(if for
(merge proposal-data {votes-for: (+ (get votes-for proposal-data) amount)})
(merge proposal-data {votes-against: (+ (get votes-against proposal-data) amount)})
)
)
(print {event: "vote", proposal: proposal, voter: tx-sender, for: for, amount: amount})
(ok true)
)
)

;; Conclusion

(define-public (conclude (proposal <proposal-trait>))
(let
(
(proposal-data (unwrap! (map-get? proposals (contract-of proposal)) err-unknown-proposal))
(passed
(match (get custom-majority proposal-data)
majority (> (* (get votes-for proposal-data) custom-majority-upper) (* (+ (get votes-for proposal-data) (get votes-against proposal-data)) majority))
(> (get votes-for proposal-data) (get votes-against proposal-data))
)
)
)
(asserts! (not (get concluded proposal-data)) err-proposal-already-concluded)
(asserts! (>= block-height (get end-block-height proposal-data)) err-end-block-height-not-reached)
(map-set proposals (contract-of proposal) (merge proposal-data {concluded: true, passed: passed}))
(print {event: "conclude", proposal: proposal, passed: passed})
(and passed (try! (contract-call? .executor-dao execute proposal tx-sender)))
(ok passed)
)
)

;; --- Extension callback

(define-public (callback (sender principal) (memo (buff 34)))
(ok true)
)
153 changes: 153 additions & 0 deletions contracts/extensions/ede008-flexible-funded-submission.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
;; Title: EDE008 Funded Custom End Proposal Submission
;; Author: Marvin Janssen & Mike Cohen
;; Depends-On: EDE001, EDE007
;; Synopsis:
;; This extension part of the core of ExecutorDAO. It allows members to
;; bring proposals to the voting phase by funding them with a preset amount
;; of tokens.
;; Description:
;; The level of funding is determined by a DAO parameter and can be changed by proposal.
;; Any funder can reclaim their stx up to the point the proposal is fully funded and submitted.
;; Proposals can also be marked as refundable in which case a funder can reclaim their stx
;; even after submission (during or after the voting period).
;; This extension provides the ability for the final funding transaction to set a
;; custom majority for voting. This changes the threshold from the
;; default of 50% to anything up to 100%.

(impl-trait .extension-trait.extension-trait)
(use-trait proposal-trait .proposal-trait.proposal-trait)

(define-constant err-unauthorised (err u3100))
(define-constant err-not-governance-token (err u3101))
(define-constant err-insufficient-balance (err u3102))
(define-constant err-unknown-parameter (err u3103))
(define-constant err-proposal-minimum-start-delay (err u3104))
(define-constant err-proposal-minimum-duration (err u3105))
(define-constant err-already-funded (err u3106))
(define-constant err-nothing-to-refund (err u3107))
(define-constant err-refund-not-allowed (err u3108))

(define-map refundable-proposals principal bool)
(define-map funded-proposals principal bool)
(define-map proposal-funding principal uint)
(define-map funding-per-principal {proposal: principal, funder: principal} uint)

(define-map parameters (string-ascii 30) uint)

(map-set parameters "funding-cost" u500000) ;; funding cost in uSTX. 5 STX in this case.
(map-set parameters "minimum-proposal-start-delay" u6) ;; eg 6 = ~1 hour minimum delay before voting on a proposal can start.
(map-set parameters "minimum-proposal-duration" u72) ;; eg 72 = ~1/2 days minimum duration of voting.

;; --- Authorisation check

(define-public (is-dao-or-extension)
(ok (asserts! (or (is-eq tx-sender .ecosystem-dao) (contract-call? .ecosystem-dao is-extension contract-caller)) err-unauthorised))
)

;; --- Internal DAO functions

;; Proposals

(define-private (submit-proposal-for-vote (proposal <proposal-trait>) (start-block-height uint) (duration uint) (custom-majority (optional uint)))
(contract-call? .ede007-snapshot-proposal-voting-v5 add-proposal
proposal
{
start-block-height: start-block-height,
end-block-height: (+ start-block-height duration),
custom-majority: custom-majority,
proposer: tx-sender ;; change to original submitter
}
)
)

;; Parameters

(define-public (set-parameter (parameter (string-ascii 30)) (value uint))
(begin
(try! (is-dao-or-extension))
(try! (get-parameter parameter))
(ok (map-set parameters parameter value))
)
)

;; Refunds

(define-public (set-refundable (proposal principal) (refundable bool))
(begin
(try! (is-dao-or-extension))
(ok (map-set refundable-proposals proposal refundable))
)
)

;; --- Public functions

;; Parameters

(define-read-only (get-parameter (parameter (string-ascii 30)))
(ok (unwrap! (map-get? parameters parameter) err-unknown-parameter))
)

;; Funding status

(define-read-only (is-proposal-funded (proposal principal))
(default-to false (map-get? funded-proposals proposal))
)

(define-read-only (get-proposal-funding (proposal principal))
(default-to u0 (map-get? proposal-funding proposal))
)

(define-read-only (get-proposal-funding-by-principal (proposal principal) (funder principal))
(default-to u0 (map-get? funding-per-principal {proposal: proposal, funder: funder}))
)

(define-read-only (can-refund (proposal principal) (funder principal))
(or
(default-to false (map-get? refundable-proposals proposal))
(and (not (is-proposal-funded proposal)) (is-eq funder tx-sender))
)
)

;; Proposals

(define-public (fund (proposal <proposal-trait>) (start-delay uint) (duration uint) (amount uint) (custom-majority (optional uint)))
(let
(
(proposal-principal (contract-of proposal))
(current-total-funding (get-proposal-funding proposal-principal))
(funding-cost (try! (get-parameter "funding-cost")))
(difference (if (> funding-cost current-total-funding) (- funding-cost current-total-funding) u0))
(funded (<= difference amount))
(transfer-amount (if funded difference amount))
)
(asserts! (not (is-proposal-funded proposal-principal)) err-already-funded)
(and (> transfer-amount u0) (try! (stx-transfer? transfer-amount tx-sender .ede006-treasury)))
(map-set funding-per-principal {proposal: proposal-principal, funder: tx-sender} (+ (get-proposal-funding-by-principal proposal-principal tx-sender) transfer-amount))
(map-set proposal-funding proposal-principal (+ current-total-funding transfer-amount))
(asserts! funded (ok false))
(asserts! (>= start-delay (try! (get-parameter "minimum-proposal-start-delay"))) err-proposal-minimum-start-delay)
(asserts! (>= duration (try! (get-parameter "minimum-proposal-duration"))) err-proposal-minimum-duration)
(map-set funded-proposals proposal-principal true)
(submit-proposal-for-vote proposal (+ block-height start-delay) duration custom-majority)
)
)

(define-public (refund (proposal principal) (funder (optional principal)))
(let
(
(recipient (default-to tx-sender funder))
(refund-amount (get-proposal-funding-by-principal proposal recipient))
)
(asserts! (> refund-amount u0) err-nothing-to-refund)
(asserts! (can-refund proposal recipient) err-refund-not-allowed)
(map-set funding-per-principal {proposal: proposal, funder: recipient} u0)
(map-set proposal-funding proposal (- (get-proposal-funding proposal) refund-amount))
(contract-call? .ede006-treasury stx-transfer refund-amount recipient none)
)
)

;; --- Extension callback

(define-public (callback (sender principal) (memo (buff 34)))
(ok true)
)