Skip to content

Commit

Permalink
Add active threshold functionality to CW4 voting module, closes DA0-D…
Browse files Browse the repository at this point in the history
  • Loading branch information
theghostmac committed Apr 6, 2024
1 parent 3ab7017 commit 56d007d
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 13 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions contracts/voting/dao-voting-cw4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dao-dao-macros = { workspace = true }
dao-interface = { workspace = true }
cw4 = { workspace = true }
cw4-group = { workspace = true }
dao-voting = { workspace = true }

[dev-dependencies]
cw-multi-test = { workspace = true }
139 changes: 128 additions & 11 deletions contracts/voting/dao-voting-cw4/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use std::ops::{Div, Mul};

use cosmwasm_std::{
to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg,
Uint128, WasmMsg,
Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg,
to_json_binary, Uint128, Uint256, WasmMsg,
};
use cw2::{get_contract_version, set_contract_version, ContractVersion};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cw2::{ContractVersion, get_contract_version, set_contract_version};
use cw4::{MemberListResponse, MemberResponse, TotalWeightResponse};
use cw_utils::parse_reply_instantiate_data;

use dao_interface::voting::IsActiveResponse;
use dao_voting::threshold::{
ActiveThreshold, assert_valid_percentage_threshold
,
};

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, GroupContract, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::state::{DAO, GROUP_CONTRACT};
use crate::state::{ACTIVE_THRESHOLD, Config, CONFIG, DAO, GROUP_CONTRACT};

pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw4";
pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand All @@ -26,6 +34,20 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

let config: Config = if let Some(active_threshold) = msg.active_threshold {
Config {
active_threshold_enabled: true,
active_threshold: Some(active_threshold),
}
} else {
Config {
active_threshold_enabled: false,
active_threshold: None,
}
};

CONFIG.save(deps.storage, &config)?;

DAO.save(deps.storage, &info.sender)?;

match msg.group_contract {
Expand Down Expand Up @@ -108,12 +130,70 @@ pub fn instantiate(

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
_deps: DepsMut,
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateActiveThreshold { new_threshold } => {
execute_update_active_threshold(deps, env, info, new_threshold)
}
}
}

pub fn execute_update_active_threshold(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: ExecuteMsg,
info: MessageInfo,
new_active_threshold: Option<ActiveThreshold>,
) -> Result<Response, ContractError> {
Err(ContractError::NoExecute {})
let dao = DAO.load(deps.storage)?;
if info.sender != dao {
return Err(ContractError::Unauthorized {});
}

if let Some(active_threshold) = &new_active_threshold {
// Pre-validation before state changes
match active_threshold {
ActiveThreshold::Percentage { percent } => {
assert_valid_percentage_threshold(*percent)?;
}
ActiveThreshold::AbsoluteCount { count } => {
if *count.is_zero() {
return Err(ContractError::InvalidThreshold {});
}
}
}
}

// Safe to modify state after validation
match new_active_threshold {
Some(threshold) => ACTIVE_THRESHOLD.save(deps.storage, &threshold)?,
None => ACTIVE_THRESHOLD.remove(deps.storage),
}

// As opposed to doing it this way:
// if let Some(active_threshold) = new_active_threshold {
// // Pre-validation before state changes.
// match active_threshold {
// ActiveThreshold::Percentage { percent } => {
// assert_valid_percentage_threshold(percent)?;
// }
// ActiveThreshold::AbsoluteCount { count } => {
// if count.is_zero() {
// return Err(ContractError::InvalidThreshold {});
// }
// }
// }
// ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?;
// } else {
// ACTIVE_THRESHOLD.remove(deps.storage);
// }

Ok(Response::new()
.add_attribute("method", "update_active_threshold")
.add_attribute("status", "success"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -126,6 +206,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
QueryMsg::Info {} => query_info(deps),
QueryMsg::GroupContract {} => to_json_binary(&GROUP_CONTRACT.load(deps.storage)?),
QueryMsg::Dao {} => to_json_binary(&DAO.load(deps.storage)?),
QueryMsg::IsActive {} => query_is_active(deps),
}
}

Expand Down Expand Up @@ -164,12 +245,48 @@ pub fn query_total_power_at_height(deps: Deps, env: Env, height: Option<u64>) ->
}

pub fn query_info(deps: Deps) -> StdResult<Binary> {
let info = cw2::get_contract_version(deps.storage)?;
let info = get_contract_version(deps.storage)?;
to_json_binary(&dao_interface::voting::InfoResponse { info })
}

pub fn query_is_active(deps: Deps) -> StdResult<Binary> {
let active_threshold = ACTIVE_THRESHOLD.may_load(deps.storage)?;

match active_threshold {
Some(ActiveThreshold::AbsoluteCount { count }) => {
let group_contract = GROUP_CONTRACT.load(deps.storage)?;
let total_weight: TotalWeightResponse = deps.querier.query_wasm_smart(
&group_contract,
&cw4_group::msg::QueryMsg::TotalWeight { at_height: None },
)?;
to_json_binary(&IsActiveResponse {
active: total_weight.weight >= count.into(),
})
}
Some(ActiveThreshold::Percentage { percent, .. }) => {
let group_contract = GROUP_CONTRACT.load(deps.storage)?;
let total_weight: TotalWeightResponse = deps.querier.query_wasm_smart(
&group_contract,
&cw4_group::msg::QueryMsg::TotalWeight { at_height: None },
)?;
let required_weight: Uint256 = Uint256::from(total_weight.weight).mul(Uint256::from(percent)).div(Uint256::from(100));

to_json_binary(&IsActiveResponse {
active: total_weight.weight >= required_weight.into(),
})
}
None => {
to_json_binary(&IsActiveResponse { active: true })
}
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
let config: Config = CONFIG.load(deps.storage)?;
// Update config as necessary
CONFIG.save(deps.storage, &config)?;

let storage_version: ContractVersion = get_contract_version(deps.storage)?;

// Only migrate if newer
Expand Down
4 changes: 4 additions & 0 deletions contracts/voting/dao-voting-cw4/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ pub enum ContractError {

#[error("Total weight of the CW4 contract cannot be zero")]
ZeroTotalWeight {},

#[error("The provided threshold is invalid: thresholds must be positive and within designated limits (e.g., percentages between 0 and 100)")]
InvalidThreshold {},

}
14 changes: 12 additions & 2 deletions contracts/voting/dao-voting-cw4/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use cosmwasm_schema::{cw_serde, QueryResponses};
use dao_dao_macros::voting_module_query;
use dao_dao_macros::{active_query, voting_module_query};
use dao_voting::threshold::ActiveThreshold;

#[cw_serde]
pub enum GroupContract {
Expand All @@ -15,11 +16,20 @@ pub enum GroupContract {
#[cw_serde]
pub struct InstantiateMsg {
pub group_contract: GroupContract,
pub active_threshold: Option<ActiveThreshold>,
}

#[cw_serde]
pub enum ExecuteMsg {}
pub enum ExecuteMsg {
/// Sets the active threshold to a new value. Only the
/// instantiator of this contract (a DAO most likely) may call this
/// method.
UpdateActiveThreshold {
new_threshold: Option<ActiveThreshold>,
},
}

#[active_query]
#[voting_module_query]
#[cw_serde]
#[derive(QueryResponses)]
Expand Down
14 changes: 14 additions & 0 deletions contracts/voting/dao-voting-cw4/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cw_storage_plus::Item;
use cw_utils::Duration;
use dao_voting::threshold::ActiveThreshold;

pub const GROUP_CONTRACT: Item<Addr> = Item::new("group_contract");
pub const DAO: Item<Addr> = Item::new("dao_address");

/// The minimum amount of users for the DAO to be active
pub const ACTIVE_THRESHOLD: Item<ActiveThreshold> = Item::new("active_threshold");

#[cw_serde]
pub struct Config {
pub active_threshold_enabled: bool,
pub active_threshold: Option<ActiveThreshold>,
}

pub const CONFIG: Item<Config> = Item::new("config");

0 comments on commit 56d007d

Please sign in to comment.