fake-dao-action-proposal-voting

SP2Z94F6QX847PMXTPJJ2ZCCN79JZDW3PJ4E6ZABY

Source Code

;; title: aibtc-action-proposal-voting
;; version: 3.0.0
;; summary: An extension that manages voting on predefined actions using a SIP-010 Stacks token.

;; traits
;;

(impl-trait 'SPW8QZNWKZGVHX012HCBJVJVPS94PXFG578P53TM.aibtc-dao-traits.extension)
(impl-trait 'SPW8QZNWKZGVHX012HCBJVJVPS94PXFG578P53TM.aibtc-dao-traits.action-proposal-voting)
(use-trait action-trait 'SPW8QZNWKZGVHX012HCBJVJVPS94PXFG578P53TM.aibtc-dao-traits.action)

;; constants
;;

(define-constant SELF (as-contract tx-sender))
(define-constant DEPLOYED_BITCOIN_BLOCK burn-block-height)
(define-constant DEPLOYED_STACKS_BLOCK stacks-block-height)

;; error messages
(define-constant ERR_NOT_DAO_OR_EXTENSION (err u1300))
(define-constant ERR_FETCHING_TOKEN_DATA (err u1301))
(define-constant ERR_INSUFFICIENT_BALANCE (err u1302))
(define-constant ERR_PROPOSAL_NOT_FOUND (err u1303))
(define-constant ERR_PROPOSAL_VOTING_ACTIVE (err u1304))
(define-constant ERR_PROPOSAL_EXECUTION_DELAY (err u1305))
(define-constant ERR_PROPOSAL_RATE_LIMIT (err u1306))
(define-constant ERR_SAVING_PROPOSAL (err u1307))
(define-constant ERR_PROPOSAL_ALREADY_CONCLUDED (err u1308))
(define-constant ERR_RETRIEVING_START_BLOCK_HASH (err u1309))
(define-constant ERR_VOTE_TOO_SOON (err u1310))
(define-constant ERR_VOTE_TOO_LATE (err u1311))
(define-constant ERR_ALREADY_VOTED (err u1312))
(define-constant ERR_INVALID_ACTION (err u1313))

(define-constant AIBTC_DAO_RUN_COST_AMOUNT u20000000000) ;; 200 DAO tokens w/ 8 decimals

(define-constant AIBTC_DAO_RUN_COST_CONTRACT 'SP2Z94F6QX847PMXTPJJ2ZCCN79JZDW3PJ4E6ZABY.aibtc-dao-run-cost) ;; AIBTC dao run cost contract
(define-constant DAO_REWARDS_ACCOUNT .fake-rewards-account) ;; rewards account for the DAO

;; voting configuration
(define-constant VOTING_QUORUM u15) ;; 15% of liquid supply must participate
(define-constant VOTING_THRESHOLD u66) ;; 66% of votes must be in favor
(define-constant VOTING_BOND u25000000000) ;; action proposal bond, 250 DAO tokens w/ 8 decimals
(define-constant VOTING_REWARD u100000000000) ;; action proposal reward, 1,000 DAO tokens w/ 8 decimals
(define-constant VOTING_TREASURY .fake-treasury) ;; used to calculate liquid supply

;; set voting delay
;; mainnet: 12 blocks (2 hours)
;; testnet: 3 blocks (30 minutes)
(define-constant VOTING_DELAY u12)
;; set voting period
;; mainnet: 24 blocks (4 hours)
;; testnet: 3 blocks (30 minutes)
(define-constant VOTING_PERIOD u24)

(define-constant REPUTATION_CHANGE u1) ;; reputation increase/decrease

;; data vars
;;

(define-data-var proposalCount uint u0) ;; total number of proposals
(define-data-var concludedProposalCount uint u0) ;; total number of concluded proposals
(define-data-var executedProposalCount uint u0) ;; total number of executed proposals

(define-data-var lastProposalStacksBlock uint DEPLOYED_STACKS_BLOCK) ;; stacks block height of last proposal created
(define-data-var lastProposalBitcoinBlock uint DEPLOYED_BITCOIN_BLOCK) ;; bitcoin block height of last proposal created

;; data maps
;;

(define-map ProposalDetails
  uint ;; proposal ID
  {
    action: principal, ;; action contract to execute
    parameters: (buff 2048), ;; parameters to pass to action contract
    bond: uint, ;; proposal bond amount
    caller: principal, ;; contract-caller
    creator: principal, ;; contract-caller
    creatorUserId: uint, ;; user index in DAO
    liquidTokens: uint, ;; liquid tokens
    memo: (optional (string-ascii 1024)), ;; memo for the proposal
  }
)

(define-map ProposalBlocks
  uint ;; proposal id
  {
    createdBtc: uint, ;; bitcoin block height
    createdStx: uint, ;; stacks block height for at-block calls
    voteStart: uint, ;; bitcoin block height
    voteEnd: uint, ;; bitcoin block height
    execStart: uint, ;; bitcoin block height
    execEnd: uint, ;; bitcoin block height
  }
)

(define-map ProposalRecords
  uint ;; proposal id
  {
    ;; accumulated in proposal life cycle
    votesFor: uint, ;; total votes for
    votesAgainst: uint, ;; total votes against
    vetoVotes: uint, ;; total veto votes
    ;; updated after conclusion
    concluded: bool, ;; was the proposal concluded
    metQuorum: bool, ;; did the proposal meet quorum
    metThreshold: bool, ;; did the proposal meet threshold
    passed: bool, ;; did the proposal pass
    executed: bool, ;; did the proposal execute
    expired: bool, ;; was the proposal expired
    vetoMetQuorum: bool, ;; did the veto meet quorum
    vetoExceedsYes: bool, ;; did the veto exceed yes votes
    vetoed: bool, ;; was the proposal vetoed
  }
)

(define-map VoteRecords
  {
    proposalId: uint, ;; proposal id
    voter: principal, ;; voter address
  }
  {
    vote: bool, ;; true for yes, false for no
    amount: uint, ;; total votes
  }
)

(define-map VetoVoteRecords
  {
    proposalId: uint, ;; proposal id
    voter: principal, ;; voter address
  }
  uint ;; total veto votes
)

;; public functions
;;

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

(define-public (create-action-proposal
    (action <action-trait>)
    (parameters (buff 2048))
    (memo (optional (string-ascii 1024)))
  )
  (let (
      (actionContract (contract-of action))
      (userId (try! (contract-call? .fake-dao-users get-or-create-user-index contract-caller)))
      (newId (+ (var-get proposalCount) u1))
      (createdStx (- stacks-block-height u1))
      (createdBtc burn-block-height)
      (liquidTokens (try! (get-liquid-supply createdStx)))
      (voteStart (+ createdBtc VOTING_DELAY))
      (voteEnd (+ voteStart VOTING_PERIOD))
      (execStart (+ voteEnd VOTING_DELAY))
      (execEnd (+ execStart VOTING_PERIOD))
      (senderBalance (unwrap! (contract-call? .fake-faktory get-balance contract-caller)
        ERR_FETCHING_TOKEN_DATA
      ))
      (validAction (is-action-valid action))
    )
    ;; liquidTokens is greater than zero
    (asserts! (> liquidTokens u0) ERR_FETCHING_TOKEN_DATA)
    ;; verify this extension and action contract are active in dao
    (asserts! validAction ERR_INVALID_ACTION)
    ;; verify the parameters are valid
    (try! (contract-call? action check-parameters parameters))
    ;; at least one btc block has passed since last proposal
    (asserts! (> createdBtc (var-get lastProposalBitcoinBlock))
      ERR_PROPOSAL_RATE_LIMIT
    )
    ;; caller has the required balance
    (asserts! (> senderBalance VOTING_BOND) ERR_INSUFFICIENT_BALANCE)
    ;; print proposal creation event
    (print {
      notification: "fake-action-proposal-voting/create-action-proposal",
      payload: {
        contractCaller: contract-caller,
        txSender: tx-sender,
        action: actionContract,
        parameters: parameters,
        bond: VOTING_BOND,
        caller: contract-caller,
        creator: contract-caller,
        creatorUserId: userId,
        liquidTokens: liquidTokens,
        memo: memo,
        createdBtc: createdBtc,
        createdStx: createdStx,
        voteStart: voteStart,
        voteEnd: voteEnd,
        execStart: execStart,
        execEnd: execEnd,
        proposalId: newId,
        votingPeriod: VOTING_PERIOD,
        votingQuorum: VOTING_QUORUM,
        votingThreshold: VOTING_THRESHOLD,
        votingDelay: VOTING_DELAY,
        votingReward: VOTING_REWARD,
      },
    })
    ;; create the proposal details
    (asserts!
      (map-insert ProposalDetails newId {
        action: actionContract,
        parameters: parameters,
        bond: VOTING_BOND,
        caller: contract-caller,
        creator: contract-caller,
        creatorUserId: userId,
        liquidTokens: liquidTokens,
        memo: memo,
      })
      ERR_SAVING_PROPOSAL
    )
    ;; create the proposal blocks
    (asserts!
      (map-insert ProposalBlocks newId {
        createdBtc: createdBtc,
        createdStx: createdStx,
        voteStart: voteStart,
        voteEnd: voteEnd,
        execStart: execStart,
        execEnd: execEnd,
      })
      ERR_SAVING_PROPOSAL
    )
    ;; create the proposal record
    (asserts!
      (map-insert ProposalRecords newId {
        votesFor: u0,
        votesAgainst: u0,
        vetoVotes: u0,
        concluded: false,
        metQuorum: false,
        metThreshold: false,
        passed: false,
        executed: false,
        expired: false,
        vetoMetQuorum: false,
        vetoExceedsYes: false,
        vetoed: false,
      })
      ERR_SAVING_PROPOSAL
    )
    ;; set last proposal created block height
    (var-set lastProposalBitcoinBlock createdBtc)
    (var-set lastProposalStacksBlock createdStx)
    ;; increment proposal count
    (var-set proposalCount newId)
    ;; transfer the proposal bond to this contract
    (try! (contract-call? .fake-faktory transfer VOTING_BOND contract-caller SELF none))
    ;; transfer the run cost fee to the run AIBTC dao cost contract
    (try! (as-contract (contract-call? .fake-treasury withdraw-ft .fake-faktory
      AIBTC_DAO_RUN_COST_AMOUNT AIBTC_DAO_RUN_COST_CONTRACT
    )))
    ;; transfer reward to the dao rewards account
    (as-contract (contract-call? .fake-treasury withdraw-ft .fake-faktory VOTING_REWARD
      DAO_REWARDS_ACCOUNT
    ))
  )
)

(define-public (vote-on-action-proposal
    (proposalId uint)
    (vote bool)
  )
  (let (
      (proposalRecord (unwrap! (map-get? ProposalRecords proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlocks (unwrap! (map-get? ProposalBlocks proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlock (get createdStx proposalBlocks))
      (proposalBlockHash (unwrap! (get-block-hash proposalBlock) ERR_RETRIEVING_START_BLOCK_HASH))
      (senderBalance (unwrap!
        (at-block proposalBlockHash
          (contract-call? .fake-faktory get-balance contract-caller)
        )
        ERR_FETCHING_TOKEN_DATA
      ))
      (userId (try! (contract-call? .fake-dao-users get-or-create-user-index contract-caller)))
      (voterRecord (map-get? VoteRecords {
        proposalId: proposalId,
        voter: contract-caller,
      }))
      (previousVote (if (is-some voterRecord)
        (some (get vote (unwrap-panic voterRecord)))
        none
      ))
      (previousVoteAmount (if (is-some voterRecord)
        (some (get amount (unwrap-panic voterRecord)))
        none
      ))
    )
    ;; caller has the required balance
    (asserts! (> senderBalance u0) ERR_INSUFFICIENT_BALANCE)
    ;; proposal was not already concluded
    (asserts! (not (get concluded proposalRecord)) ERR_PROPOSAL_ALREADY_CONCLUDED)
    ;; proposal vote is still active
    (asserts! (>= burn-block-height (get voteStart proposalBlocks))
      ERR_VOTE_TOO_SOON
    )
    (asserts! (< burn-block-height (get voteEnd proposalBlocks))
      ERR_VOTE_TOO_LATE
    )
    ;; proposal vote not already cast
    (and
      (is-some voterRecord)
      (asserts! (not (is-eq (get vote (unwrap-panic voterRecord)) vote))
        ERR_ALREADY_VOTED
      )
    )
    ;; print vote event
    (print {
      notification: "fake-action-proposal-voting/vote-on-action-proposal",
      payload: {
        contractCaller: contract-caller,
        txSender: tx-sender,
        voter: contract-caller,
        voterUserId: userId,
        proposalId: proposalId,
        amount: senderBalance,
        vote: vote,
      },
    })
    (and
      (is-some previousVote)
      ;; update the proposal record to remove the previous vote
      (map-set ProposalRecords proposalId
        (if (is-eq (unwrap-panic previousVote) true)
          (merge proposalRecord { votesFor: (- (get votesFor proposalRecord) (unwrap-panic previousVoteAmount)) })
          (merge proposalRecord { votesAgainst: (- (get votesAgainst proposalRecord) (unwrap-panic previousVoteAmount)) })
        ))
    )
    ;; update the proposal record
    (map-set ProposalRecords proposalId
      (if vote
        (merge proposalRecord { votesFor: (+ (get votesFor proposalRecord) senderBalance) })
        (merge proposalRecord { votesAgainst: (+ (get votesAgainst proposalRecord) senderBalance) })
      ))
    ;; record the vote for the sender
    (ok (map-set VoteRecords {
      proposalId: proposalId,
      voter: contract-caller,
    } {
      vote: vote,
      amount: senderBalance,
    }))
  )
)

(define-public (veto-action-proposal (proposalId uint))
  (let (
      (proposalRecord (unwrap! (map-get? ProposalRecords proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlocks (unwrap! (map-get? ProposalBlocks proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlock (get createdStx proposalBlocks))
      (proposalBlockHash (unwrap! (get-block-hash proposalBlock) ERR_RETRIEVING_START_BLOCK_HASH))
      (senderBalance (unwrap!
        (at-block proposalBlockHash
          (contract-call? .fake-faktory get-balance contract-caller)
        )
        ERR_FETCHING_TOKEN_DATA
      ))
      (userId (try! (contract-call? .fake-dao-users get-or-create-user-index contract-caller)))
    )
    ;; caller has the required balance
    (asserts! (> senderBalance u0) ERR_INSUFFICIENT_BALANCE)
    ;; proposal was not already concluded
    (asserts! (not (get concluded proposalRecord)) ERR_PROPOSAL_ALREADY_CONCLUDED)
    ;; proposal vote ended, in execution delay
    (asserts! (>= burn-block-height (get voteEnd proposalBlocks))
      ERR_VOTE_TOO_SOON
    )
    (asserts! (< burn-block-height (get execStart proposalBlocks))
      ERR_VOTE_TOO_LATE
    )
    ;; veto not already cast
    (asserts!
      (is-none (map-get? VetoVoteRecords {
        proposalId: proposalId,
        voter: contract-caller,
      }))
      ERR_ALREADY_VOTED
    )
    ;; print veto event
    (print {
      notification: "fake-action-proposal-voting/veto-action-proposal",
      payload: {
        contractCaller: contract-caller,
        txSender: tx-sender,
        vetoer: contract-caller,
        vetoerUserId: userId,
        proposalId: proposalId,
        amount: senderBalance,
      },
    })
    ;; update the proposal record
    (map-set ProposalRecords proposalId
      (merge proposalRecord { vetoVotes: (+ (get vetoVotes proposalRecord) senderBalance) })
    )
    ;; update the veto vote record for the sender
    (ok (map-set VetoVoteRecords {
      proposalId: proposalId,
      voter: contract-caller,
    }
      senderBalance
    ))
  )
)

(define-public (conclude-action-proposal
    (proposalId uint)
    (action <action-trait>)
  )
  (let (
      (actionContract (contract-of action))
      (proposalDetails (unwrap! (map-get? ProposalDetails proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlocks (unwrap! (map-get? ProposalBlocks proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalRecord (unwrap! (map-get? ProposalRecords proposalId) ERR_PROPOSAL_NOT_FOUND))
      (creator (get creator proposalDetails))
      (liquidTokens (get liquidTokens proposalDetails))
      (votesFor (get votesFor proposalRecord))
      (votesAgainst (get votesAgainst proposalRecord))
      (vetoVotes (get vetoVotes proposalRecord))
      (hasVotes (> (+ votesFor votesAgainst) u0))
      (metQuorum (and
        hasVotes
        (>= (/ (* (+ votesFor votesAgainst) u100) liquidTokens) VOTING_QUORUM)
      ))
      (metThreshold (and
        hasVotes
        (>= (/ (* votesFor u100) (+ votesFor votesAgainst)) VOTING_THRESHOLD)
      ))
      (vetoMetQuorum (and
        (> vetoVotes u0)
        (>= (/ (* vetoVotes u100) liquidTokens) VOTING_QUORUM)
      ))
      (vetoExceedsYes (> vetoVotes votesFor))
      (vetoActivated (and vetoMetQuorum vetoExceedsYes))
      ;; evaluate criteria to determine if proposal passed
      (votePassed (and
        hasVotes ;; check if there are any votes
        metQuorum ;; quorum: total votes vs liquid supply
        metThreshold ;; threshold: enough yes votes vs total votes
        (not vetoActivated) ;; veto: reached quorum and more than yes votes
      ))
      ;; check info for running action
      (validAction (is-action-valid action))
      (burnBlock burn-block-height)
      (notExpired (< burnBlock (get execEnd proposalBlocks)))
      (tryToExecute (and
        votePassed
        validAction
        notExpired
      ))
    )
    ;; proposal not already concluded
    (asserts! (not (get concluded proposalRecord)) ERR_PROPOSAL_ALREADY_CONCLUDED)
    ;; proposal is past voting period
    (asserts! (>= burnBlock (get voteEnd proposalBlocks))
      ERR_PROPOSAL_VOTING_ACTIVE
    )
    ;; proposal is past execution delay
    (asserts! (>= burnBlock (get execStart proposalBlocks))
      ERR_PROPOSAL_EXECUTION_DELAY
    )
    ;; action must be the same as the one in proposal
    (asserts! (is-eq (get action proposalDetails) actionContract)
      ERR_INVALID_ACTION
    )
    ;; record user in dao if not already
    (try! (contract-call? .fake-dao-users get-or-create-user-index contract-caller))
    ;; print conclusion event
    (print {
      notification: "fake-action-proposal-voting/conclude-action-proposal",
      payload: {
        contractCaller: contract-caller,
        txSender: tx-sender,
        action: actionContract,
        parameters: (get parameters proposalDetails),
        bond: (get bond proposalDetails),
        creator: creator,
        creatorUserId: (get creatorUserId proposalDetails),
        liquidTokens: liquidTokens,
        memo: (get memo proposalDetails),
        proposalId: proposalId,
        votesFor: votesFor,
        votesAgainst: votesAgainst,
        vetoVotes: vetoVotes,
        metQuorum: metQuorum,
        metThreshold: metThreshold,
        vetoMetQuorum: vetoMetQuorum,
        vetoExceedsYes: vetoExceedsYes,
        vetoed: vetoActivated,
        passed: votePassed,
        expired: (not notExpired),
        executed: tryToExecute,
      },
    })
    ;; update the proposal record
    (map-set ProposalRecords proposalId
      (merge proposalRecord {
        concluded: true,
        metQuorum: metQuorum,
        metThreshold: metThreshold,
        passed: votePassed,
        expired: (not notExpired),
        executed: tryToExecute,
        vetoMetQuorum: vetoMetQuorum,
        vetoExceedsYes: vetoExceedsYes,
        vetoed: vetoActivated,
      })
    )
    ;; transfer the bond based on the outcome
    (if votePassed
      (try! (as-contract (contract-call? .fake-faktory transfer (get bond proposalDetails) SELF
        creator none
      )))
      (try! (as-contract (contract-call? .fake-faktory transfer (get bond proposalDetails) SELF
        VOTING_TREASURY none
      )))
    )
    ;; update the users reputation based on outcome
    (if votePassed
      (try! (contract-call? .fake-dao-users increase-user-reputation creator
        REPUTATION_CHANGE
      ))
      (try! (contract-call? .fake-dao-users decrease-user-reputation creator
        REPUTATION_CHANGE
      ))
    )
    ;; increment the concluded proposal count
    (var-set concludedProposalCount (+ (var-get concludedProposalCount) u1))
    ;; try to execute the action if the proposal passed
    (ok (if tryToExecute
      (and
        ;; increment the executed proposal count
        (var-set executedProposalCount (+ (var-get executedProposalCount) u1))
        ;; try to run the action
        (match (contract-call? action run (get parameters proposalDetails))
          ;; running the action succeeded
          ok_
          (try! (as-contract (contract-call? .fake-rewards-account transfer-reward creator
            VOTING_REWARD
          )))
          ;; return false and print error on failure
          err_
          (begin
            (print {
              notification: "fake-action-proposal-voting/conclude-action-proposal",
              payload: { executionError: err_ },
            })
            (try! (as-contract (contract-call? .fake-rewards-account transfer-reward
              VOTING_TREASURY VOTING_REWARD
            )))
            false
          )
        )
      )
      false
    ))
  )
)

;; read only functions
;;

(define-read-only (get-voting-power
    (proposalId uint)
    (voter principal)
  )
  (let (
      (proposalBlocks (unwrap! (map-get? ProposalBlocks proposalId) ERR_PROPOSAL_NOT_FOUND))
      (proposalBlockHash (unwrap! (get-block-hash (get createdStx proposalBlocks))
        ERR_RETRIEVING_START_BLOCK_HASH
      ))
    )
    (at-block proposalBlockHash (contract-call? .fake-faktory get-balance voter))
  )
)

(define-read-only (get-proposal (proposalId uint))
  (let (
      (proposalDetails (unwrap! (map-get? ProposalDetails proposalId) none))
      (proposalBlocks (unwrap! (map-get? ProposalBlocks proposalId) none))
      (proposalRecord (unwrap! (map-get? ProposalRecords proposalId) none))
    )
    (some (merge proposalDetails (merge proposalBlocks proposalRecord)))
  )
)

(define-read-only (get-vote-record
    (proposalId uint)
    (voter principal)
  )
  (map-get? VoteRecords {
    proposalId: proposalId,
    voter: voter,
  })
)

(define-read-only (get-veto-vote-record
    (proposalId uint)
    (voter principal)
  )
  (map-get? VetoVoteRecords {
    proposalId: proposalId,
    voter: voter,
  })
)

(define-read-only (get-vote-records
    (proposalId uint)
    (voter principal)
  )
  {
    voteRecord: (get-vote-record proposalId voter),
    vetoVoteRecord: (get-veto-vote-record proposalId voter),
  }
)

(define-read-only (get-total-proposals)
  {
    proposalCount: (var-get proposalCount),
    concludedProposalCount: (var-get concludedProposalCount),
    executedProposalCount: (var-get executedProposalCount),
    lastProposalStacksBlock: (var-get lastProposalStacksBlock),
    lastProposalBitcoinBlock: (var-get lastProposalBitcoinBlock),
  }
)

(define-read-only (get-voting-configuration)
  {
    self: SELF,
    deployedBitcoinBlock: DEPLOYED_BITCOIN_BLOCK,
    deployedStacksBlock: DEPLOYED_STACKS_BLOCK,
    delay: VOTING_DELAY,
    period: VOTING_PERIOD,
    quorum: VOTING_QUORUM,
    threshold: VOTING_THRESHOLD,
    treasury: VOTING_TREASURY,
    proposalBond: VOTING_BOND,
    proposalReward: VOTING_REWARD,
  }
)

;; calculate the liquid supply of the dao token at a past stacks block height
(define-read-only (get-liquid-supply (blockHeight uint))
  (let (
      (blockHash (unwrap! (get-block-hash blockHeight) ERR_RETRIEVING_START_BLOCK_HASH))
      (totalSupply (unwrap!
        (at-block blockHash (contract-call? .fake-faktory get-total-supply))
        ERR_FETCHING_TOKEN_DATA
      ))
      (treasuryBalance (unwrap!
        (at-block blockHash
          (contract-call? .fake-faktory get-balance VOTING_TREASURY)
        )
        ERR_FETCHING_TOKEN_DATA
      ))
    )
    (ok (- totalSupply treasuryBalance))
  )
)

;; private functions
;;
(define-private (is-dao-or-extension)
  (ok (asserts!
    (or
      (is-eq tx-sender .fake-base-dao)
      (contract-call? .fake-base-dao is-extension contract-caller)
    )
    ERR_NOT_DAO_OR_EXTENSION
  ))
)

(define-private (is-action-valid (action <action-trait>))
  (let (
      (extensionActive (is-ok (as-contract (is-dao-or-extension))))
      (actionActive (contract-call? .fake-base-dao is-extension (contract-of action)))
    )
    (and extensionActive actionActive)
  )
)

(define-private (get-block-hash (blockHeight uint))
  (get-stacks-block-info? id-header-hash blockHeight)
)

Functions (10)

FunctionAccessArgs
callbackpublicsender: principal, memo: (buff 34
create-action-proposalpublicaction: <action-trait>, parameters: (buff 2048
veto-action-proposalpublicproposalId: uint
get-proposalread-onlyproposalId: uint
get-total-proposalsread-only
get-voting-configurationread-only
get-liquid-supplyread-onlyblockHeight: uint
is-dao-or-extensionprivate
is-action-validprivateaction: <action-trait>
get-block-hashprivateblockHeight: uint