Skip to content

Commit

Permalink
cw3-flex-multisig: Bring back common Ballot, Proposal etc. implementa…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
ueco-jb committed Dec 22, 2021
1 parent 8422834 commit bf72c54
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 6 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion contracts/cw3-flex-multisig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ library = []
utils = { path = "../../packages/utils", version = "0.10.3" }
cw2 = { path = "../../packages/cw2", version = "0.10.3" }
cw3 = { path = "../../packages/cw3", version = "0.10.3" }
cw3-fixed-multisig = { path = "../cw3-fixed-multisig", version = "0.10.3" }
cw4 = { path = "../../packages/cw4", version = "0.10.3" }
cw-storage-plus = { path = "../../packages/storage-plus", version = "0.10.3" }
cosmwasm-std = { version = "1.0.0-beta3" }
Expand Down
3 changes: 1 addition & 2 deletions contracts/cw3-flex-multisig/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ use cw3::{
ProposalListResponse, ProposalResponse, Status, Vote, VoteInfo, VoteListResponse, VoteResponse,
VoterDetail, VoterListResponse, VoterResponse,
};
use cw3_fixed_multisig::state::{next_id, Ballot, Proposal, Votes, BALLOTS, PROPOSALS};
use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff};
use cw_storage_plus::Bound;
use utils::{maybe_addr, Expiration, ThresholdResponse};

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{Config, CONFIG};
use crate::state::{next_id, Ballot, Config, Proposal, Votes, BALLOTS, CONFIG, PROPOSALS};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig";
Expand Down
145 changes: 143 additions & 2 deletions contracts/cw3-flex-multisig/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storage, Uint128};

use cw3::{Status, Vote};
use cw4::Cw4Contract;
use cw_storage_plus::Item;
use utils::{Duration, Threshold};
use cw_storage_plus::{Item, Map};
use utils::{Duration, Expiration, Threshold};

// we multiply by this when calculating needed_votes in order to round up properly
// Note: `10u128.pow(9)` fails as "u128::pow` is not yet stable as a const fn"
const PRECISION_FACTOR: u128 = 1_000_000_000;

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Config {
Expand All @@ -13,5 +20,139 @@ pub struct Config {
pub group_addr: Cw4Contract,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Proposal {
pub title: String,
pub description: String,
pub start_height: u64,
pub expires: Expiration,
pub msgs: Vec<CosmosMsg<Empty>>,
pub status: Status,
/// pass requirements
pub threshold: Threshold,
// the total weight when the proposal started (used to calculate percentages)
pub total_weight: u64,
// summary of existing votes
pub votes: Votes,
}

// weight of votes for each option
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Votes {
pub yes: u64,
pub no: u64,
pub abstain: u64,
pub veto: u64,
}

impl Votes {
/// sum of all votes
pub fn total(&self) -> u64 {
self.yes + self.no + self.abstain + self.veto
}

/// create it with a yes vote for this much
pub fn yes(init_weight: u64) -> Self {
Votes {
yes: init_weight,
no: 0,
abstain: 0,
veto: 0,
}
}

pub fn add_vote(&mut self, vote: Vote, weight: u64) {
match vote {
Vote::Yes => self.yes += weight,
Vote::Abstain => self.abstain += weight,
Vote::No => self.no += weight,
Vote::Veto => self.veto += weight,
}
}
}

impl Proposal {
/// current_status is non-mutable and returns what the status should be.
/// (designed for queries)
pub fn current_status(&self, block: &BlockInfo) -> Status {
let mut status = self.status;

// if open, check if voting is passed or timed out
if status == Status::Open && self.is_passed(block) {
status = Status::Passed;
}
if status == Status::Open && self.expires.is_expired(block) {
status = Status::Rejected;
}

status
}

/// update_status sets the status of the proposal to current_status.
/// (designed for handler logic)
pub fn update_status(&mut self, block: &BlockInfo) {
self.status = self.current_status(block);
}

// returns true iff this proposal is sure to pass (even before expiration if no future
// sequence of possible votes can cause it to fail)
pub fn is_passed(&self, block: &BlockInfo) -> bool {
match self.threshold {
Threshold::AbsoluteCount {
weight: weight_needed,
} => self.votes.yes >= weight_needed,
Threshold::AbsolutePercentage {
percentage: percentage_needed,
} => {
self.votes.yes
>= votes_needed(self.total_weight - self.votes.abstain, percentage_needed)
}
Threshold::ThresholdQuorum { threshold, quorum } => {
// we always require the quorum
if self.votes.total() < votes_needed(self.total_weight, quorum) {
return false;
}
if self.expires.is_expired(block) {
// If expired, we compare Yes votes against the total number of votes (minus abstain).
let opinions = self.votes.total() - self.votes.abstain;
self.votes.yes >= votes_needed(opinions, threshold)
} else {
// If not expired, we must assume all non-votes will be cast as No.
// We compare threshold against the total weight (minus abstain).
let possible_opinions = self.total_weight - self.votes.abstain;
self.votes.yes >= votes_needed(possible_opinions, threshold)
}
}
}
}
}

// this is a helper function so Decimal works with u64 rather than Uint128
// also, we must *round up* here, as we need 8, not 7 votes to reach 50% of 15 total
fn votes_needed(weight: u64, percentage: Decimal) -> u64 {
let applied = percentage * Uint128::new(PRECISION_FACTOR * weight as u128);
// Divide by PRECISION_FACTOR, rounding up to the nearest integer
((applied.u128() + PRECISION_FACTOR - 1) / PRECISION_FACTOR) as u64
}

// we cast a ballot with our chosen vote and a given weight
// stored under the key that voted
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Ballot {
pub weight: u64,
pub vote: Vote,
}

// unique items
pub const CONFIG: Item<Config> = Item::new("config");
pub const PROPOSAL_COUNT: Item<u64> = Item::new("proposal_count");

// multiple-item map
pub const BALLOTS: Map<(u64, &Addr), Ballot> = Map::new("votes");
pub const PROPOSALS: Map<u64, Proposal> = Map::new("proposals");

pub fn next_id(store: &mut dyn Storage) -> StdResult<u64> {
let id: u64 = PROPOSAL_COUNT.may_load(store)?.unwrap_or_default() + 1;
PROPOSAL_COUNT.save(store, &id)?;
Ok(id)
}

0 comments on commit bf72c54

Please sign in to comment.