use cosmwasm_std::{Addr, CosmosMsg, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::error::MarsError;
use crate::helpers::decimal_param_le_one;
use crate::math::decimal::Decimal;
use self::error::ContractError;
pub const MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 50;
pub const MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 100;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
pub address_provider_address: Addr,
pub proposal_voting_period: u64,
pub proposal_effective_delay: u64,
pub proposal_expiration_period: u64,
pub proposal_required_deposit: Uint128,
pub proposal_required_quorum: Decimal,
pub proposal_required_threshold: Decimal,
}
impl Config {
pub fn validate(&self) -> Result<(), ContractError> {
decimal_param_le_one(&self.proposal_required_quorum, "proposal_required_quorum")?;
let minimum_proposal_required_threshold =
Decimal::percent(MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE);
let maximum_proposal_required_threshold =
Decimal::percent(MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE);
if !(self.proposal_required_threshold >= minimum_proposal_required_threshold
&& self.proposal_required_threshold <= maximum_proposal_required_threshold)
{
return Err(MarsError::InvalidParam {
param_name: "proposal_required_threshold".to_string(),
invalid_value: self.proposal_required_threshold.to_string(),
predicate: format!(
">= {} and <= {}",
minimum_proposal_required_threshold, maximum_proposal_required_threshold
),
}
.into());
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GlobalState {
pub proposal_count: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Proposal {
pub proposal_id: u64,
pub submitter_address: Addr,
pub status: ProposalStatus,
pub for_votes: Uint128,
pub against_votes: Uint128,
pub start_height: u64,
pub end_height: u64,
pub title: String,
pub description: String,
pub link: Option<String>,
pub messages: Option<Vec<ProposalMessage>>,
pub deposit_amount: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ProposalMessage {
pub execution_order: u64,
pub msg: CosmosMsg,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProposalStatus {
Active,
Passed,
Rejected,
Executed,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ProposalVote {
pub option: ProposalVoteOption,
pub power: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProposalVoteOption {
For,
Against,
}
impl std::fmt::Display for ProposalVoteOption {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_str = match self {
ProposalVoteOption::For => "for",
ProposalVoteOption::Against => "against",
};
write!(f, "{}", display_str)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ProposalsListResponse {
pub proposal_count: u64,
pub proposal_list: Vec<Proposal>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ProposalVotesResponse {
pub proposal_id: u64,
pub votes: Vec<ProposalVoteResponse>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ProposalVoteResponse {
pub voter_address: String,
pub option: ProposalVoteOption,
pub power: Uint128,
}
pub mod msg {
use cosmwasm_std::Uint128;
use cw20::Cw20ReceiveMsg;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::math::decimal::Decimal;
use super::{ProposalMessage, ProposalVoteOption};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub config: CreateOrUpdateConfig,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)]
pub struct CreateOrUpdateConfig {
pub address_provider_address: Option<String>,
pub proposal_voting_period: Option<u64>,
pub proposal_effective_delay: Option<u64>,
pub proposal_expiration_period: Option<u64>,
pub proposal_required_deposit: Option<Uint128>,
pub proposal_required_quorum: Option<Decimal>,
pub proposal_required_threshold: Option<Decimal>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Receive(Cw20ReceiveMsg),
CastVote {
proposal_id: u64,
vote: ProposalVoteOption,
},
EndProposal { proposal_id: u64 },
ExecuteProposal { proposal_id: u64 },
UpdateConfig { config: CreateOrUpdateConfig },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ReceiveMsg {
SubmitProposal {
title: String,
description: String,
link: Option<String>,
messages: Option<Vec<ProposalMessage>>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Config {},
Proposals {
start: Option<u64>,
limit: Option<u32>,
},
Proposal {
proposal_id: u64,
},
ProposalVotes {
proposal_id: u64,
start_after: Option<String>,
limit: Option<u32>,
},
}
}
pub mod error {
use cosmwasm_std::StdError;
use thiserror::Error;
use crate::error::MarsError;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("{0}")]
Mars(#[from] MarsError),
#[error("Invalid Proposal: {error:?}")]
InvalidProposal { error: String },
#[error("Proposal is not active")]
ProposalNotActive {},
#[error("User has already voted on this proposal")]
VoteUserAlreadyVoted {},
#[error("User has no voting power at block: {block:?}")]
VoteNoVotingPower { block: u64 },
#[error("Voting period has ended")]
VoteVotingPeriodEnded {},
#[error("Voting period has not ended")]
EndProposalVotingPeriodNotEnded {},
#[error("Proposal has not passed or has already been executed")]
ExecuteProposalNotPassed {},
#[error("Proposal must end it's delay period in order to be executed")]
ExecuteProposalDelayNotEnded {},
#[error("Proposal has expired")]
ExecuteProposalExpired {},
}
impl ContractError {
pub fn invalid_proposal<S: Into<String>>(error: S) -> ContractError {
ContractError::InvalidProposal {
error: error.into(),
}
}
}
}