From bb64e2f831146e069577230a3dc413efe5afe127 Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Thu, 14 Dec 2023 12:59:09 +0000 Subject: [PATCH 1/2] committing flexible funded proposal --- .../ede007-snapshot-proposal-voting-v5.clar | 151 +++++++++++++++++ .../ede008-flexible-funded-submission.clar | 153 ++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 contracts/extensions/ede007-snapshot-proposal-voting-v5.clar create mode 100644 contracts/extensions/ede008-flexible-funded-submission.clar diff --git a/contracts/extensions/ede007-snapshot-proposal-voting-v5.clar b/contracts/extensions/ede007-snapshot-proposal-voting-v5.clar new file mode 100644 index 0000000..dacda8b --- /dev/null +++ b/contracts/extensions/ede007-snapshot-proposal-voting-v5.clar @@ -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 ) (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 )) + (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) +) diff --git a/contracts/extensions/ede008-flexible-funded-submission.clar b/contracts/extensions/ede008-flexible-funded-submission.clar new file mode 100644 index 0000000..41a629c --- /dev/null +++ b/contracts/extensions/ede008-flexible-funded-submission.clar @@ -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 .executor-dao) (contract-call? .executor-dao is-extension contract-caller)) err-unauthorised)) +) + +;; --- Internal DAO functions + +;; Proposals + +(define-private (submit-proposal-for-vote (proposal ) (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 ) (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) +) From 33238f2b2406792d37765b0245bb5bac1e51c998 Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Tue, 16 Jan 2024 09:15:45 +0000 Subject: [PATCH 2/2] Flexible endtime submission extension --- contracts/extensions/ede008-flexible-funded-submission.clar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/extensions/ede008-flexible-funded-submission.clar b/contracts/extensions/ede008-flexible-funded-submission.clar index 41a629c..404f1a8 100644 --- a/contracts/extensions/ede008-flexible-funded-submission.clar +++ b/contracts/extensions/ede008-flexible-funded-submission.clar @@ -41,7 +41,7 @@ ;; --- 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)) + (ok (asserts! (or (is-eq tx-sender .ecosystem-dao) (contract-call? .ecosystem-dao is-extension contract-caller)) err-unauthorised)) ) ;; --- Internal DAO functions