Skip to content

Commit

Permalink
Merge pull request #180 from CosmWasm/add-threshold-to-cw3-flex
Browse files Browse the repository at this point in the history
Add thresholds to cw3 flex multisig (#180)
  • Loading branch information
maurolacy authored Dec 16, 2020
2 parents 2c715ec + f33e3aa commit a3e414e
Show file tree
Hide file tree
Showing 23 changed files with 1,530 additions and 187 deletions.
48 changes: 36 additions & 12 deletions contracts/cw3-fixed-multisig/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use cw0::{maybe_canonical, Expiration};
use cw2::set_contract_version;
use cw3::{
ProposalListResponse, ProposalResponse, Status, ThresholdResponse, Vote, VoteInfo,
VoteListResponse, VoteResponse, VoterInfo, VoterListResponse, VoterResponse,
VoteListResponse, VoteResponse, VoterDetail, VoterListResponse, VoterResponse,
};
use cw_storage_plus::Bound;

Expand Down Expand Up @@ -292,22 +292,28 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
fn query_threshold(deps: Deps) -> StdResult<ThresholdResponse> {
let cfg = CONFIG.load(deps.storage)?;
Ok(ThresholdResponse::AbsoluteCount {
weight_needed: cfg.required_weight,
weight: cfg.required_weight,
total_weight: cfg.total_weight,
})
}

fn query_proposal(deps: Deps, env: Env, id: u64) -> StdResult<ProposalResponse> {
let prop = PROPOSALS.load(deps.storage, id.into())?;
let status = prop.current_status(&env.block);

let cfg = CONFIG.load(deps.storage)?;
let threshold = ThresholdResponse::AbsoluteCount {
weight: cfg.required_weight,
total_weight: cfg.total_weight,
};
Ok(ProposalResponse {
id,
title: prop.title,
description: prop.description,
msgs: prop.msgs,
expires: prop.expires,
// TODO: check
status,
expires: prop.expires,
threshold,
})
}

Expand All @@ -321,12 +327,18 @@ fn list_proposals(
start_after: Option<u64>,
limit: Option<u32>,
) -> StdResult<ProposalListResponse> {
let cfg = CONFIG.load(deps.storage)?;
let threshold = ThresholdResponse::AbsoluteCount {
weight: cfg.required_weight,
total_weight: cfg.total_weight,
};

let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive_int);
let props: StdResult<Vec<_>> = PROPOSALS
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|p| map_proposal(&env.block, p))
.map(|p| map_proposal(&env.block, &threshold, p))
.collect();

Ok(ProposalListResponse { proposals: props? })
Expand All @@ -338,19 +350,26 @@ fn reverse_proposals(
start_before: Option<u64>,
limit: Option<u32>,
) -> StdResult<ProposalListResponse> {
let cfg = CONFIG.load(deps.storage)?;
let threshold = ThresholdResponse::AbsoluteCount {
weight: cfg.required_weight,
total_weight: cfg.total_weight,
};

let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let end = start_before.map(Bound::exclusive_int);
let props: StdResult<Vec<_>> = PROPOSALS
.range(deps.storage, None, end, Order::Descending)
.take(limit)
.map(|p| map_proposal(&env.block, p))
.map(|p| map_proposal(&env.block, &threshold, p))
.collect();

Ok(ProposalListResponse { proposals: props? })
}

fn map_proposal(
block: &BlockInfo,
threshold: &ThresholdResponse,
item: StdResult<(Vec<u8>, Proposal)>,
) -> StdResult<ProposalResponse> {
let (key, prop) = item?;
Expand All @@ -360,15 +379,20 @@ fn map_proposal(
title: prop.title,
description: prop.description,
msgs: prop.msgs,
expires: prop.expires,
status,
expires: prop.expires,
threshold: threshold.clone(),
})
}

fn query_vote(deps: Deps, proposal_id: u64, voter: HumanAddr) -> StdResult<VoteResponse> {
let voter_raw = deps.api.canonical_address(&voter)?;
let prop = BALLOTS.may_load(deps.storage, (proposal_id.into(), &voter_raw))?;
let vote = prop.map(|b| b.vote);
let ballot = BALLOTS.may_load(deps.storage, (proposal_id.into(), &voter_raw))?;
let vote = ballot.map(|b| VoteInfo {
voter,
vote: b.vote,
weight: b.weight,
});
Ok(VoteResponse { vote })
}

Expand Down Expand Up @@ -400,10 +424,10 @@ fn list_votes(
Ok(VoteListResponse { votes: votes? })
}

fn query_voter(deps: Deps, voter: HumanAddr) -> StdResult<VoterInfo> {
fn query_voter(deps: Deps, voter: HumanAddr) -> StdResult<VoterResponse> {
let voter_raw = deps.api.canonical_address(&voter)?;
let weight = VOTERS.may_load(deps.storage, &voter_raw)?;
Ok(VoterInfo { weight })
Ok(VoterResponse { weight })
}

fn list_voters(
Expand All @@ -421,7 +445,7 @@ fn list_voters(
.take(limit)
.map(|item| {
let (key, weight) = item?;
Ok(VoterResponse {
Ok(VoterDetail {
addr: api.human_address(&CanonicalAddr::from(key))?,
weight,
})
Expand Down
2 changes: 1 addition & 1 deletion contracts/cw3-flex-multisig/schema/handle_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
}
},
{
"description": "handle update hook messages from the group contract",
"description": "Handles update hook messages from the group contract",
"type": "object",
"required": [
"member_changed_hook"
Expand Down
83 changes: 78 additions & 5 deletions contracts/cw3-flex-multisig/schema/init_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"required": [
"group_addr",
"max_voting_period",
"required_weight"
"threshold"
],
"properties": {
"group_addr": {
Expand All @@ -14,13 +14,15 @@
"max_voting_period": {
"$ref": "#/definitions/Duration"
},
"required_weight": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
"threshold": {
"$ref": "#/definitions/Threshold"
}
},
"definitions": {
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Duration": {
"description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined",
"anyOf": [
Expand Down Expand Up @@ -54,6 +56,77 @@
},
"HumanAddr": {
"type": "string"
},
"Threshold": {
"description": "This defines the different ways tallies can happen.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation). See also `ThresholdResponse` in the cw3 spec.",
"anyOf": [
{
"description": "Declares that a fixed weight of Yes votes is needed to pass. See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.",
"type": "object",
"required": [
"absolute_count"
],
"properties": {
"absolute_count": {
"type": "object",
"required": [
"weight"
],
"properties": {
"weight": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
}
}
},
{
"description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.",
"type": "object",
"required": [
"absolute_percentage"
],
"properties": {
"absolute_percentage": {
"type": "object",
"required": [
"percentage"
],
"properties": {
"percentage": {
"$ref": "#/definitions/Decimal"
}
}
}
}
},
{
"description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.",
"type": "object",
"required": [
"threshold_quorum"
],
"properties": {
"threshold_quorum": {
"type": "object",
"required": [
"quorum",
"threshold"
],
"properties": {
"quorum": {
"$ref": "#/definitions/Decimal"
},
"threshold": {
"$ref": "#/definitions/Decimal"
}
}
}
}
}
]
}
}
}
Loading

0 comments on commit a3e414e

Please sign in to comment.