Vote Module

This document provides a detailed overview of the "Vote Module" smart contract, which builds on the foundational cw3-fixed by utilizing a cw4 (group) contract to manage voter sets. This allows for dynamic and flexible account configurations which can be adapted for different governance structures and thresholds.

Contract Specification

The CW3 Flexible is designed to manage account operations with a dynamic voter set, allowing multiple accounts with different thresholds to be backed by the same group. This flexibility supports various administrative and operational scenarios, such as changing voter sets without redeploying the account or having different thresholds for different types of decisions.

Instantiation

The contract is instantiated with the address to an existing cw4-group contract and configured with specific administrative settings.

  • Threshold: The threshold to pass a vote or finish it. It can be of many configurations, absolute count, percentage or by a quorum threshold.

  • Max Voting Period: The maximum duration that a proposal can remain open before it is closed.

  • Executor: The address that must execute a proposal when passed. If not specified, anyone can execute a successfull proposal.

  • Proposal deposit: The amount of tokens needed to create a proposal. You can specify the amount, token and if refund on unsuccessful proposals.

  • Group address: The address of the contract that defines/contains the members. Should be the membership contract.

  • Wallet address: The address of the wallet contract where to forward messages.

  • Sender: Utility from the factory to let know the creator of the contract. Can be undefined.

  • Revoting: Allows a member to vote more than once in the same proposal.

  • Automatic execution: When set to true, any proposal that passes will be automatically executed.

  • Open proposal submission: When set to true, anyone can submit a proposal. This can be combined with fees on proposal to control spam and quality of proposals.

  • Open proposal deposit: When set to true along with the open proposal submission, then proposals will need a deposit for open users (non-members).

#[cw_serde]
pub struct InstantiateMsg {
    pub threshold: Threshold,
    pub max_voting_period: Duration,
    pub executor: Option<Executor>,
    pub proposal_deposit: Option<UncheckedDepositInfo>,
    pub group_addr: String,
    pub wallet_addr: String,
    pub sender: Option<String>,
    pub revoting: bool,
    pub automatic_execution: bool,
    // who is allowed to submit proposal, only members or anyone
    pub open_proposal_submission: bool,
    // The fee of creating a proposal for non member
    pub open_proposal_deposit: Option<UncheckedDepositInfo>,
}

Execute Messages

The contract supports various operations to manage proposals and the voting process:

  • Propose: Submits a new proposal, automatically casting a "Yes" vote by the proposer.

  • Vote: Casts a vote on an active proposal.

  • Vote with Signature: Casts a vote on an active proposal. Anyone can call this function, but must have a valid signature of a member and a message to send.

  • Execute: Attempts to execute a passed proposal.

  • Close: Marks a proposal as closed after its voting period has ended.

  • Member changed hook: Its a callback that should come from the membership contract. Currently, does nothing.

  • Update threshold: Updates the threshold for a proposal to be passed or rejected.

  • Update settings: Message to update all settings of the voting module at once. For information about each setting, refer to the instantiate message.

#[cw_serde]
pub enum ExecuteMsg {
    Propose {
        title: String,
        description: String,
        msgs: Vec<CosmosMsg>,
        latest: Option<Expiration>,
    },
    Vote {
        proposal_id: u64,
        vote: Vote,
    },
    VoteWithSigature {
        signature: SignedMessage<SignedVote>,
    },
    Execute {
        proposal_id: u64,
    },
    Close {
        proposal_id: u64,
    },
    /// Handles update hook messages from the group contract
    MemberChangedHook(MemberChangedHookMsg),
    UpdateThreshold {
        threshold: Threshold,
    },
    UpdateSettings {
        threshold: Option<Threshold>,
        max_voting_period: Option<Duration>,
        proposal_deposit: Option<UncheckedDepositInfo>,
        revoting: bool,
        automatic_execution: bool,
        open_proposal_submission: bool,
        open_proposal_deposit: Option<UncheckedDepositInfo>,
    },
}

Query Messages

  • Threshold: Returns the current voting threshold necessary for proposals to be accepted.

  • Proposal: Fetches detailed information about a specific proposal identified by its unique ID.

  • List proposals: Retrieves a list of proposals with pagination support. This can be used to view active or past proposals.

  • Reverse proposals: Lists proposals in reverse order. This is useful for retrieving the most recent proposals first.

  • Vote: Provides details about a specific vote by a voter on a particular proposal.

  • List votes: Lists all votes cast for a particular proposal, supporting pagination.

  • Voter: Retrieves details about a specific voter, including their voting weight and participation history.

  • List voters: Lists all registered voters with pagination support, which is useful for understanding the participant landscape.

  • Config: Gets the current configuration settings of the contract. This includes the voting threshold, maximum voting period, and other operational parameters.

Rust definitions:

#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
    #[returns(cw_utils::ThresholdResponse)]
    Threshold {},
    #[returns(cw3::ProposalResponse)]
    Proposal { proposal_id: u64 },
    #[returns(cw3::ProposalListResponse)]
    ListProposals {
        start_after: Option<u64>,
        limit: Option<u32>,
    },
    #[returns(cw3::ProposalListResponse)]
    ReverseProposals {
        start_before: Option<u64>,
        limit: Option<u32>,
    },
    #[returns(cw3::VoteResponse)]
    Vote { proposal_id: u64, voter: String },
    #[returns(cw3::VoteListResponse)]
    ListVotes {
        proposal_id: u64,
        start_after: Option<String>,
        limit: Option<u32>,
    },
    #[returns(cw3::VoterResponse)]
    Voter { address: String },
    #[returns(cw3::VoterListResponse)]
    ListVoters {
        start_after: Option<String>,
        limit: Option<u32>,
    },
    #[returns(crate::state::Config)]
    Config {},
}

In SDK

The TypeScript SDK provides a user-friendly interface to interact with the CW3 Flexible Multisig contract using the SigningCosmWasmClient.

Initialization

import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { Vote } from "nomos-v2";

const client: SigningCosmWasmClient = // Initialize client
const voteModule = new Vote(client,"<signer>", "<vote_module>");

SDK Methods

Here's the completed SDK documentation for the query and transaction methods of the "VoteClient" class in Markdown format. This SDK is designed to interact with a CW3 Flexible Account contract, utilizing a comprehensive set of functionalities for both querying and executing on the blockchain.

Query Methods

  • threshold(): Retrieves the current voting threshold for proposals.

  • proposal({ proposalId }): Provides details about a specific proposal, identified by its proposal ID.

  • listProposals({ limit, startAfter }): Lists proposals with optional pagination, where limit determines the maximum number of proposals returned, and startAfter specifies the proposal ID after which to start listing.

  • reverseProposals({ limit, startBefore }): Lists proposals in reverse order from a specified starting point, useful for fetching the most recent proposals first.

  • queryVote({ proposalId, voter }): Retrieves details about a specific vote cast by a voter on a proposal.

  • listVotes({ limit, proposalId, startAfter }): Lists all votes cast on a specific proposal, with pagination.

  • voter({ address }): Provides details about a specific voter, including their voting weight and history.

  • listVoters({ limit, startAfter }): Lists all voters with optional pagination.

  • config(): Retrieves the current configuration settings of the contract.

Transaction Methods

  • propose({ title, description, msgs, latest }): Submits a new proposal with the specified title, description, list of messages (msgs), and an optional expiration date (latest).

  • vote({ proposalId, vote }): Submits a vote on a proposal identified by proposalId. The vote parameter specifies the voter's decision.

  • voteWithSignature({ signature }): Submits a vote with a digital signature, allowing for authenticated voting without direct interaction.

  • execute({ proposalId }): Executes the operations specified in a passed proposal, identified by proposalId.

  • close({ proposalId }): Closes a proposal that has either been executed or has expired, identified by proposalId.

  • memberChangedHook({ diffs }): Notifies the contract of changes in membership, where diffs is an array detailing changes.

  • updateThreshold({ threshold }): Updates the voting threshold required for proposals to pass.

  • updateSettings({ automaticExecution, maxVotingPeriod, openProposalDeposit, openProposalSubmission, proposalDeposit, revoting, threshold }): Updates various contract settings, including voting periods, deposit requirements, and more.

Usage Example

Below is an example of how to use these methods to interact with the "VoteClient":

import { Vote } from "nomos-v2";
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";

// Initialization of the client and VoteClient
const client: SigningCosmWasmClient = new SigningCosmWasmClient(...);
const voteClient = new Vote(client, "<signer_address>", "<vote_module>");

// Proposing a new change
await voteClient.propose({
  title: "Update Protocol",
  description: "Proposal to update the protocol to version 2.0",
  msgs: [...],
});

// Voting on the proposal
await voteClient.vote({
  proposalId: 1,
  vote: "yes" //can only be "yes" | "no" | "abstain" | "veto"
});

// Executing a proposal that has passed
await voteClient.execute({
  proposalId: 1
});

// Closing an expired proposal
await voteClient.close({
  proposalId: 1
});

Last updated