1use cosmwasm_std::{Addr, CosmosMsg, Uint128};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::error::MarsError;
6use crate::helpers::decimal_param_le_one;
7use crate::math::decimal::Decimal;
8
9use self::error::ContractError;
10
11pub const MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 50;
12pub const MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 100;
13
14#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
16pub struct Config {
17 pub address_provider_address: Addr,
19 pub proposal_voting_period: u64,
21 pub proposal_effective_delay: u64,
24 pub proposal_expiration_period: u64,
26 pub proposal_required_deposit: Uint128,
29 pub proposal_required_quorum: Decimal,
31 pub proposal_required_threshold: Decimal,
33}
34
35impl Config {
36 pub fn validate(&self) -> Result<(), ContractError> {
37 decimal_param_le_one(&self.proposal_required_quorum, "proposal_required_quorum")?;
38
39 let minimum_proposal_required_threshold =
40 Decimal::percent(MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE);
41 let maximum_proposal_required_threshold =
42 Decimal::percent(MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE);
43
44 if !(self.proposal_required_threshold >= minimum_proposal_required_threshold
45 && self.proposal_required_threshold <= maximum_proposal_required_threshold)
46 {
47 return Err(MarsError::InvalidParam {
48 param_name: "proposal_required_threshold".to_string(),
49 invalid_value: self.proposal_required_threshold.to_string(),
50 predicate: format!(
51 ">= {} and <= {}",
52 minimum_proposal_required_threshold, maximum_proposal_required_threshold
53 ),
54 }
55 .into());
56 }
57
58 Ok(())
59 }
60}
61
62#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
64pub struct GlobalState {
65 pub proposal_count: u64,
67}
68
69#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
71pub struct Proposal {
72 pub proposal_id: u64,
73 pub submitter_address: Addr,
75 pub status: ProposalStatus,
77 pub for_votes: Uint128,
79 pub against_votes: Uint128,
81 pub start_height: u64,
83 pub end_height: u64,
85 pub title: String,
87 pub description: String,
89 pub link: Option<String>,
92 pub messages: Option<Vec<ProposalMessage>>,
94 pub deposit_amount: Uint128,
97}
98
99#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
101pub struct ProposalMessage {
102 pub execution_order: u64,
104 pub msg: CosmosMsg,
106}
107
108#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
110#[serde(rename_all = "snake_case")]
111pub enum ProposalStatus {
112 Active,
114 Passed,
116 Rejected,
118 Executed,
120}
121
122#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
124pub struct ProposalVote {
125 pub option: ProposalVoteOption,
127 pub power: Uint128,
129}
130
131#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
132#[serde(rename_all = "snake_case")]
133pub enum ProposalVoteOption {
134 For,
135 Against,
136}
137
138impl std::fmt::Display for ProposalVoteOption {
139 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
140 let display_str = match self {
141 ProposalVoteOption::For => "for",
142 ProposalVoteOption::Against => "against",
143 };
144 write!(f, "{}", display_str)
145 }
146}
147
148#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
149pub struct ProposalsListResponse {
150 pub proposal_count: u64,
152 pub proposal_list: Vec<Proposal>,
154}
155
156#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
157pub struct ProposalVotesResponse {
158 pub proposal_id: u64,
159 pub votes: Vec<ProposalVoteResponse>,
160}
161
162#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
163pub struct ProposalVoteResponse {
164 pub voter_address: String,
165 pub option: ProposalVoteOption,
166 pub power: Uint128,
167}
168
169pub mod msg {
170 use cosmwasm_std::Uint128;
171 use cw20::Cw20ReceiveMsg;
172 use schemars::JsonSchema;
173 use serde::{Deserialize, Serialize};
174
175 use crate::math::decimal::Decimal;
176
177 use super::{ProposalMessage, ProposalVoteOption};
178
179 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
180 pub struct InstantiateMsg {
181 pub config: CreateOrUpdateConfig,
182 }
183
184 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)]
185 pub struct CreateOrUpdateConfig {
186 pub address_provider_address: Option<String>,
187
188 pub proposal_voting_period: Option<u64>,
189 pub proposal_effective_delay: Option<u64>,
190 pub proposal_expiration_period: Option<u64>,
191 pub proposal_required_deposit: Option<Uint128>,
192 pub proposal_required_quorum: Option<Decimal>,
193 pub proposal_required_threshold: Option<Decimal>,
194 }
195
196 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
197 #[serde(rename_all = "snake_case")]
198 pub enum ExecuteMsg {
199 Receive(Cw20ReceiveMsg),
201
202 CastVote {
204 proposal_id: u64,
205 vote: ProposalVoteOption,
206 },
207
208 EndProposal { proposal_id: u64 },
210
211 ExecuteProposal { proposal_id: u64 },
213
214 UpdateConfig { config: CreateOrUpdateConfig },
216 }
217
218 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
219 #[serde(rename_all = "snake_case")]
220 pub enum ReceiveMsg {
221 SubmitProposal {
224 title: String,
225 description: String,
226 link: Option<String>,
227 messages: Option<Vec<ProposalMessage>>,
228 },
229 }
230
231 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
232 #[serde(rename_all = "snake_case")]
233 pub enum QueryMsg {
234 Config {},
235 Proposals {
236 start: Option<u64>,
237 limit: Option<u32>,
238 },
239 Proposal {
240 proposal_id: u64,
241 },
242 ProposalVotes {
243 proposal_id: u64,
244 start_after: Option<String>,
245 limit: Option<u32>,
246 },
247 }
248}
249
250pub mod error {
251 use cosmwasm_std::StdError;
252 use thiserror::Error;
253
254 use crate::error::MarsError;
255
256 #[derive(Error, Debug, PartialEq)]
257 pub enum ContractError {
258 #[error("{0}")]
259 Std(#[from] StdError),
260
261 #[error("{0}")]
262 Mars(#[from] MarsError),
263
264 #[error("Invalid Proposal: {error:?}")]
265 InvalidProposal { error: String },
266
267 #[error("Proposal is not active")]
268 ProposalNotActive {},
269
270 #[error("User has already voted on this proposal")]
271 VoteUserAlreadyVoted {},
272 #[error("User has no voting power at block: {block:?}")]
273 VoteNoVotingPower { block: u64 },
274 #[error("Voting period has ended")]
275 VoteVotingPeriodEnded {},
276
277 #[error("Voting period has not ended")]
278 EndProposalVotingPeriodNotEnded {},
279
280 #[error("Proposal has not passed or has already been executed")]
281 ExecuteProposalNotPassed {},
282 #[error("Proposal must end it's delay period in order to be executed")]
283 ExecuteProposalDelayNotEnded {},
284 #[error("Proposal has expired")]
285 ExecuteProposalExpired {},
286 }
287
288 impl ContractError {
289 pub fn invalid_proposal<S: Into<String>>(error: S) -> ContractError {
290 ContractError::InvalidProposal {
291 error: error.into(),
292 }
293 }
294 }
295}