1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4 to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult,
5 SubMsg, Uint128, WasmMsg,
6};
7use cw2::set_contract_version;
8use cw_utils::parse_reply_instantiate_data;
9
10use crate::error::ContractError;
11use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
12use crate::state::{DAO_ADDRESS, GROUP_CONTRACT, TOTAL_WEIGHT, USER_WEIGHTS};
13
14const CONTRACT_NAME: &str = "crates.io:cw4-voting";
15const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
16
17const INSTANTIATE_GROUP_REPLY_ID: u64 = 0;
18
19#[cfg_attr(not(feature = "library"), entry_point)]
20pub fn instantiate(
21 deps: DepsMut,
22 env: Env,
23 info: MessageInfo,
24 msg: InstantiateMsg,
25) -> Result<Response, ContractError> {
26 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
27 if msg.initial_members.is_empty() {
28 return Err(ContractError::NoMembers {});
29 }
30 let original_len = msg.initial_members.len();
31 let mut initial_members = msg.initial_members;
32 initial_members.sort_by(|a, b| a.addr.cmp(&b.addr));
33 initial_members.dedup();
34 let new_len = initial_members.len();
35
36 if original_len != new_len {
37 return Err(ContractError::DuplicateMembers {});
38 }
39
40 let mut total_weight = Uint128::zero();
41 for member in initial_members.iter() {
42 let member_addr = deps.api.addr_validate(&member.addr)?;
43 if member.weight > 0 {
44 let weight = Uint128::from(member.weight);
47 USER_WEIGHTS.save(deps.storage, &member_addr, &weight, env.block.height)?;
48 total_weight += weight;
49 }
50 }
51
52 if total_weight.is_zero() {
53 return Err(ContractError::ZeroTotalWeight {});
54 }
55 TOTAL_WEIGHT.save(deps.storage, &total_weight, env.block.height)?;
56
57 let msg = WasmMsg::Instantiate {
59 admin: Some(info.sender.to_string()),
60 code_id: msg.cw4_group_code_id,
61 msg: to_binary(&cw4_group::msg::InstantiateMsg {
62 admin: Some(env.contract.address.to_string()),
63 members: initial_members,
64 })?,
65 funds: vec![],
66 label: env.contract.address.to_string(),
67 };
68
69 let msg = SubMsg::reply_on_success(msg, INSTANTIATE_GROUP_REPLY_ID);
70
71 DAO_ADDRESS.save(deps.storage, &info.sender)?;
72
73 Ok(Response::new()
74 .add_attribute("action", "instantiate")
75 .add_submessage(msg))
76}
77
78#[cfg_attr(not(feature = "library"), entry_point)]
79pub fn execute(
80 deps: DepsMut,
81 env: Env,
82 info: MessageInfo,
83 msg: ExecuteMsg,
84) -> Result<Response, ContractError> {
85 match msg {
86 ExecuteMsg::MemberChangedHook { diffs } => {
87 execute_member_changed_hook(deps, env, info, diffs)
88 }
89 }
90}
91
92pub fn execute_member_changed_hook(
93 deps: DepsMut,
94 env: Env,
95 info: MessageInfo,
96 diffs: Vec<cw4::MemberDiff>,
97) -> Result<Response, ContractError> {
98 let group_contract = GROUP_CONTRACT.load(deps.storage)?;
99 if info.sender != group_contract {
100 return Err(ContractError::Unauthorized {});
101 }
102
103 let total_weight = TOTAL_WEIGHT.load(deps.storage)?;
104 let mut positive_difference: Uint128 = Uint128::zero();
107 let mut negative_difference: Uint128 = Uint128::zero();
108 for diff in diffs {
109 let user_address = deps.api.addr_validate(&diff.key)?;
110 let weight = diff.new.unwrap_or_default();
111 let old = diff.old.unwrap_or_default();
112 if weight > old {
114 positive_difference += Uint128::from(weight - old);
115 } else {
116 negative_difference += Uint128::from(old - weight);
117 }
118
119 if weight != 0 {
120 USER_WEIGHTS.save(
121 deps.storage,
122 &user_address,
123 &Uint128::from(weight),
124 env.block.height,
125 )?;
126 } else if weight == 0 && weight != old {
127 USER_WEIGHTS.remove(deps.storage, &user_address, env.block.height)?;
133 }
134 }
135 let new_total_weight = total_weight
136 .checked_add(positive_difference)
137 .map_err(StdError::overflow)?
138 .checked_sub(negative_difference)
139 .map_err(StdError::overflow)?;
140 TOTAL_WEIGHT.save(deps.storage, &new_total_weight, env.block.height)?;
141
142 Ok(Response::new()
143 .add_attribute("action", "member_changed_hook")
144 .add_attribute("total_weight", new_total_weight.to_string()))
145}
146
147#[cfg_attr(not(feature = "library"), entry_point)]
148pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
149 match msg {
150 QueryMsg::VotingPowerAtHeight { address, height } => {
151 query_voting_power_at_height(deps, env, address, height)
152 }
153 QueryMsg::TotalPowerAtHeight { height } => query_total_power_at_height(deps, env, height),
154 QueryMsg::Info {} => query_info(deps),
155 QueryMsg::GroupContract {} => to_binary(&GROUP_CONTRACT.load(deps.storage)?),
156 QueryMsg::Dao {} => to_binary(&DAO_ADDRESS.load(deps.storage)?),
157 }
158}
159
160pub fn query_voting_power_at_height(
161 deps: Deps,
162 env: Env,
163 address: String,
164 height: Option<u64>,
165) -> StdResult<Binary> {
166 let address = deps.api.addr_validate(&address)?;
167 let height = height.unwrap_or(env.block.height);
168 let power = USER_WEIGHTS
169 .may_load_at_height(deps.storage, &address, height)?
170 .unwrap_or_default();
171
172 to_binary(&cw_core_interface::voting::VotingPowerAtHeightResponse { power, height })
173}
174
175pub fn query_total_power_at_height(deps: Deps, env: Env, height: Option<u64>) -> StdResult<Binary> {
176 let height = height.unwrap_or(env.block.height);
177 let power = TOTAL_WEIGHT
178 .may_load_at_height(deps.storage, height)?
179 .unwrap_or_default();
180 to_binary(&cw_core_interface::voting::TotalPowerAtHeightResponse { power, height })
181}
182
183pub fn query_info(deps: Deps) -> StdResult<Binary> {
184 let info = cw2::get_contract_version(deps.storage)?;
185 to_binary(&cw_core_interface::voting::InfoResponse { info })
186}
187
188#[cfg_attr(not(feature = "library"), entry_point)]
189pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
190 Ok(Response::default())
192}
193
194#[cfg_attr(not(feature = "library"), entry_point)]
195pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> {
196 match msg.id {
197 INSTANTIATE_GROUP_REPLY_ID => {
198 let res = parse_reply_instantiate_data(msg);
199 match res {
200 Ok(res) => {
201 let group_contract = GROUP_CONTRACT.may_load(deps.storage)?;
202 if group_contract.is_some() {
203 return Err(ContractError::DuplicateGroupContract {});
204 }
205 let group_contract = deps.api.addr_validate(&res.contract_address)?;
206 let dao_address = DAO_ADDRESS.load(deps.storage)?;
207 GROUP_CONTRACT.save(deps.storage, &group_contract)?;
208 let msg1 = WasmMsg::Execute {
209 contract_addr: group_contract.to_string(),
210 msg: to_binary(&cw4_group::msg::ExecuteMsg::AddHook {
211 addr: env.contract.address.to_string(),
212 })?,
213 funds: vec![],
214 };
215 let msg2 = WasmMsg::Execute {
217 contract_addr: group_contract.to_string(),
218 msg: to_binary(&cw4_group::msg::ExecuteMsg::UpdateAdmin {
219 admin: Some(dao_address.to_string()),
220 })?,
221 funds: vec![],
222 };
223 Ok(Response::default()
224 .add_attribute("group_contract_address", group_contract)
225 .add_message(msg1)
226 .add_message(msg2))
227 }
228 Err(_) => Err(ContractError::GroupContractInstantiateError {}),
229 }
230 }
231 _ => Err(ContractError::UnknownReplyId { id: msg.id }),
232 }
233}