Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate cw3-fixed-multisig #119

Merged
merged 13 commits into from
Oct 12, 2020
6 changes: 3 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion contracts/cw3-fixed-multisig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ library = []
cw0 = { path = "../../packages/cw0", version = "0.2.3" }
cw2 = { path = "../../packages/cw2", version = "0.2.3" }
cw3 = { path = "../../packages/cw3", version = "0.2.3" }
cw-storage-plus = { path = "../../packages/storage-plus", version = "0.2.3", features = ["iterator"] }
cosmwasm-std = { version = "0.11.0", features = ["iterator"] }
cosmwasm-storage = { version = "0.11.0", features = ["iterator"] }
schemars = "0.7"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.20" }
Expand Down
96 changes: 51 additions & 45 deletions contracts/cw3-fixed-multisig/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ use cosmwasm_std::{
HandleResponse, HumanAddr, InitResponse, MessageInfo, Order, Querier, StdResult, Storage,
};

use cw0::{calc_range_start_human, Expiration};
use cw0::{maybe_canonical, Expiration};
use cw2::set_contract_version;
use cw3::{
ProposalListResponse, ProposalResponse, Status, ThresholdResponse, Vote, VoteInfo,
VoteListResponse, VoteResponse, VoterListResponse, VoterResponse,
};
use cw_storage_plus::{Bound, OwnedBound};

use crate::error::ContractError;
use crate::msg::{HandleMsg, InitMsg, QueryMsg};
use crate::state::{
ballots, ballots_read, config, config_read, next_id, parse_id, proposal, proposal_read, voters,
voters_read, Ballot, Config, Proposal,
next_id, parse_id, Ballot, Config, Proposal, BALLOTS, CONFIG, PROPOSALS, VOTERS,
};

// version info for migration info
Expand Down Expand Up @@ -48,13 +48,12 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
total_weight,
max_voting_period: msg.max_voting_period,
};
config(&mut deps.storage).save(&cfg)?;
CONFIG.save(&mut deps.storage, &cfg)?;

// add all voters
let mut bucket = voters(&mut deps.storage);
for voter in msg.voters.iter() {
let key = deps.api.canonical_address(&voter.addr)?;
bucket.save(key.as_slice(), &voter.weight)?;
VOTERS.save(&mut deps.storage, &key, &voter.weight)?;
}
Ok(InitResponse::default())
}
Expand Down Expand Up @@ -90,11 +89,11 @@ pub fn handle_propose<S: Storage, A: Api, Q: Querier>(
) -> Result<HandleResponse<Empty>, ContractError> {
// only members of the multisig can create a proposal
let raw_sender = deps.api.canonical_address(&info.sender)?;
let vote_power = voters_read(&deps.storage)
.may_load(raw_sender.as_slice())?
let vote_power = VOTERS
.may_load(&deps.storage, &raw_sender)?
.ok_or_else(|| ContractError::Unauthorized {})?;

let cfg = config_read(&deps.storage).load()?;
let cfg = CONFIG.load(&deps.storage)?;

// max expires also used as default
let max_expires = cfg.max_voting_period.after(&env.block);
Expand Down Expand Up @@ -123,14 +122,14 @@ pub fn handle_propose<S: Storage, A: Api, Q: Querier>(
required_weight: cfg.required_weight,
};
let id = next_id(&mut deps.storage)?;
proposal(&mut deps.storage).save(&id.to_be_bytes(), &prop)?;
PROPOSALS.save(&mut deps.storage, id.into(), &prop)?;

// add the first yes vote from voter
let ballot = Ballot {
weight: vote_power,
vote: Vote::Yes,
};
ballots(&mut deps.storage, id).save(raw_sender.as_slice(), &ballot)?;
BALLOTS.save(&mut deps.storage, (id.into(), &raw_sender), &ballot)?;

Ok(HandleResponse {
messages: vec![],
Expand All @@ -153,12 +152,12 @@ pub fn handle_vote<S: Storage, A: Api, Q: Querier>(
) -> Result<HandleResponse<Empty>, ContractError> {
// only members of the multisig can vote
let raw_sender = deps.api.canonical_address(&info.sender)?;
let vote_power = voters_read(&deps.storage)
.may_load(raw_sender.as_slice())?
let vote_power = VOTERS
.may_load(&deps.storage, &raw_sender)?
.ok_or_else(|| ContractError::Unauthorized {})?;

// ensure proposal exists and can be voted on
let mut prop = proposal_read(&deps.storage).load(&proposal_id.to_be_bytes())?;
let mut prop = PROPOSALS.load(&deps.storage, proposal_id.into())?;
if prop.status != Status::Open {
return Err(ContractError::NotOpen {});
}
Expand All @@ -167,13 +166,17 @@ pub fn handle_vote<S: Storage, A: Api, Q: Querier>(
}

// cast vote if no vote previously cast
ballots(&mut deps.storage, proposal_id).update(raw_sender.as_slice(), |bal| match bal {
Some(_) => Err(ContractError::AlreadyVoted {}),
None => Ok(Ballot {
weight: vote_power,
vote,
}),
})?;
BALLOTS.update(
&mut deps.storage,
(proposal_id.into(), &raw_sender),
|bal| match bal {
Some(_) => Err(ContractError::AlreadyVoted {}),
None => Ok(Ballot {
weight: vote_power,
vote,
}),
},
)?;

// if yes vote, update tally
if vote == Vote::Yes {
Expand All @@ -182,7 +185,7 @@ pub fn handle_vote<S: Storage, A: Api, Q: Querier>(
if prop.yes_weight >= prop.required_weight {
prop.status = Status::Passed;
}
proposal(&mut deps.storage).save(&proposal_id.to_be_bytes(), &prop)?;
PROPOSALS.save(&mut deps.storage, proposal_id.into(), &prop)?;
}

Ok(HandleResponse {
Expand All @@ -205,7 +208,7 @@ pub fn handle_execute<S: Storage, A: Api, Q: Querier>(
) -> Result<HandleResponse, ContractError> {
// anyone can trigger this if the vote passed

let mut prop = proposal_read(&deps.storage).load(&proposal_id.to_be_bytes())?;
let mut prop = PROPOSALS.load(&deps.storage, proposal_id.into())?;
// we allow execution even after the proposal "expiration" as long as all vote come in before
// that point. If it was approved on time, it can be executed any time.
if prop.status != Status::Passed {
Expand All @@ -214,7 +217,7 @@ pub fn handle_execute<S: Storage, A: Api, Q: Querier>(

// set it to executed
prop.status = Status::Executed;
proposal(&mut deps.storage).save(&proposal_id.to_be_bytes(), &prop)?;
PROPOSALS.save(&mut deps.storage, proposal_id.into(), &prop)?;

// dispatch all proposed messages
Ok(HandleResponse {
Expand All @@ -236,7 +239,7 @@ pub fn handle_close<S: Storage, A: Api, Q: Querier>(
) -> Result<HandleResponse<Empty>, ContractError> {
// anyone can trigger this if the vote passed

let mut prop = proposal_read(&deps.storage).load(&proposal_id.to_be_bytes())?;
let mut prop = PROPOSALS.load(&deps.storage, proposal_id.into())?;
if [Status::Executed, Status::Rejected, Status::Passed]
.iter()
.any(|x| *x == prop.status)
Expand All @@ -249,7 +252,7 @@ pub fn handle_close<S: Storage, A: Api, Q: Querier>(

// set it to failed
prop.status = Status::Rejected;
proposal(&mut deps.storage).save(&proposal_id.to_be_bytes(), &prop)?;
PROPOSALS.save(&mut deps.storage, proposal_id.into(), &prop)?;

Ok(HandleResponse {
messages: vec![],
Expand Down Expand Up @@ -293,7 +296,7 @@ pub fn query<S: Storage, A: Api, Q: Querier>(
fn query_threshold<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
) -> StdResult<ThresholdResponse> {
let cfg = config_read(&deps.storage).load()?;
let cfg = CONFIG.load(&deps.storage)?;
Ok(ThresholdResponse::AbsoluteCount {
weight_needed: cfg.required_weight,
total_weight: cfg.total_weight,
Expand All @@ -305,7 +308,7 @@ fn query_proposal<S: Storage, A: Api, Q: Querier>(
env: Env,
id: u64,
) -> StdResult<ProposalResponse> {
let prop = proposal_read(&deps.storage).load(&id.to_be_bytes())?;
let prop = PROPOSALS.load(&deps.storage, id.into())?;
let status = prop.current_status(&env.block);
Ok(ProposalResponse {
id,
Expand All @@ -329,9 +332,9 @@ fn list_proposals<S: Storage, A: Api, Q: Querier>(
limit: Option<u32>,
) -> StdResult<ProposalListResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = start_after.map(|id| (id + 1).to_be_bytes().to_vec());
let props: StdResult<Vec<_>> = proposal_read(&deps.storage)
.range(start.as_deref(), None, Order::Ascending)
let start = OwnedBound::exclusive_int(start_after);
let props: StdResult<Vec<_>> = PROPOSALS
.range(&deps.storage, start.bound(), Bound::None, Order::Ascending)
.take(limit)
.map(|p| map_proposal(&env.block, p))
.collect();
Expand All @@ -346,9 +349,9 @@ fn reverse_proposals<S: Storage, A: Api, Q: Querier>(
limit: Option<u32>,
) -> StdResult<ProposalListResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let end = start_before.map(|id| id.to_be_bytes().to_vec());
let props: StdResult<Vec<_>> = proposal_read(&deps.storage)
.range(None, end.as_deref(), Order::Descending)
let end = OwnedBound::exclusive_int(start_before);
let props: StdResult<Vec<_>> = PROPOSALS
.range(&deps.storage, Bound::None, end.bound(), Order::Descending)
.take(limit)
.map(|p| map_proposal(&env.block, p))
.collect();
Expand Down Expand Up @@ -378,7 +381,7 @@ fn query_vote<S: Storage, A: Api, Q: Querier>(
voter: HumanAddr,
) -> StdResult<VoteResponse> {
let voter_raw = deps.api.canonical_address(&voter)?;
let prop = ballots_read(&deps.storage, proposal_id).may_load(voter_raw.as_slice())?;
let prop = BALLOTS.may_load(&deps.storage, (proposal_id.into(), &voter_raw))?;
let vote = prop.map(|b| b.vote);
Ok(VoteResponse { vote })
}
Expand All @@ -390,11 +393,13 @@ fn list_votes<S: Storage, A: Api, Q: Querier>(
limit: Option<u32>,
) -> StdResult<VoteListResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = calc_range_start_human(deps.api, start_after)?;
let api = &deps.api;
let canon = maybe_canonical(deps.api, start_after)?;
let start = OwnedBound::exclusive(canon);

let votes: StdResult<Vec<_>> = ballots_read(&deps.storage, proposal_id)
.range(start.as_deref(), None, Order::Ascending)
let api = &deps.api;
let votes: StdResult<Vec<_>> = BALLOTS
.prefix(proposal_id.into())
.range(&deps.storage, start.bound(), Bound::None, Order::Ascending)
.take(limit)
.map(|item| {
let (key, ballot) = item?;
Expand All @@ -414,8 +419,8 @@ fn query_voter<S: Storage, A: Api, Q: Querier>(
voter: HumanAddr,
) -> StdResult<VoterResponse> {
let voter_raw = deps.api.canonical_address(&voter)?;
let weight = voters_read(&deps.storage)
.may_load(voter_raw.as_slice())?
let weight = VOTERS
.may_load(&deps.storage, &voter_raw)?
.unwrap_or_default();
Ok(VoterResponse {
addr: voter,
Expand All @@ -429,11 +434,12 @@ fn list_voters<S: Storage, A: Api, Q: Querier>(
limit: Option<u32>,
) -> StdResult<VoterListResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = calc_range_start_human(deps.api, start_after)?;
let api = &deps.api;
let canon = maybe_canonical(deps.api, start_after)?;
let start = OwnedBound::exclusive(canon);

let voters: StdResult<Vec<_>> = voters_read(&deps.storage)
.range(start.as_deref(), None, Order::Ascending)
let api = &deps.api;
let voters: StdResult<Vec<_>> = VOTERS
.range(&deps.storage, start.bound(), Bound::None, Order::Ascending)
.take(limit)
.map(|item| {
let (key, weight) = item?;
Expand Down
62 changes: 13 additions & 49 deletions contracts/cw3-fixed-multisig/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;

use cosmwasm_std::{BlockInfo, CosmosMsg, Empty, ReadonlyStorage, StdError, StdResult, Storage};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use cosmwasm_std::{BlockInfo, CosmosMsg, Empty, StdError, StdResult, Storage};

use cw0::{Duration, Expiration};
use cw3::{Status, Vote};
use cw_storage_plus::{Item, Map, U64Key};

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Config {
Expand Down Expand Up @@ -55,41 +53,18 @@ pub struct Ballot {
pub vote: Vote,
}

pub const CONFIG_KEY: &[u8] = b"config";
pub const PROPOSAL_COUNTER: &[u8] = b"proposal_count";

pub const PREFIX_PROPOSAL: &[u8] = b"proposals";
pub const PREFIX_VOTERS: &[u8] = b"voters";
pub const PREFIX_VOTES: &[u8] = b"votes";
// unique items
pub const CONFIG: Item<Config> = Item::new(b"config");
pub const PROPOSAL_COUNT: Item<u64> = Item::new(b"proposal_count");

pub fn config<S: Storage>(storage: &mut S) -> Singleton<S, Config> {
singleton(storage, CONFIG_KEY)
}

pub fn config_read<S: ReadonlyStorage>(storage: &S) -> ReadonlySingleton<S, Config> {
singleton_read(storage, CONFIG_KEY)
}

pub fn voters<S: Storage>(storage: &mut S) -> Bucket<S, u64> {
bucket(storage, PREFIX_VOTERS)
}

pub fn voters_read<S: ReadonlyStorage>(storage: &S) -> ReadonlyBucket<S, u64> {
bucket_read(storage, PREFIX_VOTERS)
}
// multiple-item maps
pub const VOTERS: Map<&[u8], u64> = Map::new(b"voters");
pub const PROPOSALS: Map<U64Key, Proposal> = Map::new(b"proposals");
pub const BALLOTS: Map<(U64Key, &[u8]), Ballot> = Map::new(b"votes");

pub fn proposal<S: Storage>(storage: &mut S) -> Bucket<S, Proposal> {
bucket(storage, PREFIX_PROPOSAL)
}

pub fn proposal_read<S: ReadonlyStorage>(storage: &S) -> ReadonlyBucket<S, Proposal> {
bucket_read(storage, PREFIX_PROPOSAL)
}

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

Expand All @@ -101,14 +76,3 @@ pub fn parse_id(data: &[u8]) -> StdResult<u64> {
)),
}
}

pub fn ballots<S: Storage>(storage: &mut S, proposal_id: u64) -> Bucket<S, Ballot> {
Bucket::multilevel(storage, &[PREFIX_VOTES, &proposal_id.to_be_bytes()])
}

pub fn ballots_read<S: ReadonlyStorage>(
storage: &S,
proposal_id: u64,
) -> ReadonlyBucket<S, Ballot> {
ReadonlyBucket::multilevel(storage, &[PREFIX_VOTES, &proposal_id.to_be_bytes()])
}
4 changes: 3 additions & 1 deletion packages/cw0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ mod pagination;

pub use crate::balance::NativeBalance;
pub use crate::expiration::{Duration, Expiration, DAY, HOUR, WEEK};
pub use pagination::{calc_range_end_human, calc_range_start_human, calc_range_start_string};
pub use pagination::{
calc_range_end_human, calc_range_start_human, calc_range_start_string, maybe_canonical,
};
10 changes: 9 additions & 1 deletion packages/cw0/src/pagination.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
use cosmwasm_std::{Api, HumanAddr, StdResult};
use cosmwasm_std::{Api, CanonicalAddr, HumanAddr, StdResult};

// this is used for pagination. Maybe we move it into the std lib one day?
pub fn maybe_canonical<A: Api>(
api: A,
human: Option<HumanAddr>,
) -> StdResult<Option<CanonicalAddr>> {
human.map(|x| api.canonical_address(&x)).transpose()
}

// this will set the first key after the provided key, by appending a 0 byte
pub fn calc_range_start_human<A: Api>(
Expand Down
Loading