1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use cosmwasm_std::{to_binary, Addr, CosmosMsg, Deps, StdResult, Uint128, WasmMsg};
use cw_storage_plus::{Item, Map};
use cw_utils::Duration;

use indexable_hooks::Hooks;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use dao_voting::{Threshold, Vote};

use crate::{
    msg::{DepositInfo, DepositToken},
    proposal::Proposal,
};

/// Counterpart to the `DepositInfo` struct which has been processed.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct CheckedDepositInfo {
    /// The address of the cw20 token to be used for proposal
    /// deposits.
    pub token: Addr,
    /// The number of tokens that must be deposited to create a
    /// proposal.
    pub deposit: Uint128,
    /// If failed proposals should have their deposits refunded.
    pub refund_failed_proposals: bool,
}

/// The governance module's configuration.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
    /// The threshold a proposal must reach to complete.
    pub threshold: Threshold,
    /// The default maximum amount of time a proposal may be voted on
    /// before expiring.
    pub max_voting_period: Duration,
    /// The minimum amount of time a proposal must be open before
    /// passing. A proposal may fail before this amount of time has
    /// elapsed, but it will not pass. This can be useful for
    /// preventing governance attacks wherein an attacker aquires a
    /// large number of tokens and forces a proposal through.
    pub min_voting_period: Option<Duration>,
    /// If set to true only members may execute passed
    /// proposals. Otherwise, any address may execute a passed
    /// proposal.
    pub only_members_execute: bool,
    /// Allows changing votes before the proposal expires. If this is
    /// enabled proposals will not be able to complete early as final
    /// vote information is not known until the time of proposal
    /// expiration.
    pub allow_revoting: bool,
    /// The address of the DAO that this governance module is
    /// associated with.
    pub dao: Addr,
    /// Information about the depost required to create a
    /// proposal. None if no deposit is required, Some otherwise.
    pub deposit_info: Option<CheckedDepositInfo>,
}

/// A vote cast for a proposal.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Ballot {
    /// The amount of voting power behind the vote.
    pub power: Uint128,
    /// The position.
    pub vote: Vote,
}

pub const CONFIG: Item<Config> = Item::new("config");
pub const PROPOSAL_COUNT: Item<u64> = Item::new("proposal_count");
pub const PROPOSALS: Map<u64, Proposal> = Map::new("proposals");
pub const BALLOTS: Map<(u64, Addr), Ballot> = Map::new("ballots");
pub const PROPOSAL_HOOKS: Hooks = Hooks::new("proposal_hooks");
pub const VOTE_HOOKS: Hooks = Hooks::new("vote_hooks");

impl DepositInfo {
    /// Converts deposit info into checked deposit info.
    pub fn into_checked(self, deps: Deps, dao: Addr) -> StdResult<CheckedDepositInfo> {
        let Self {
            token,
            deposit,
            refund_failed_proposals,
        } = self;
        let token = match token {
            DepositToken::Token { address } => deps.api.addr_validate(&address)?,
            DepositToken::VotingModuleToken {} => {
                let voting_module: Addr = deps
                    .querier
                    .query_wasm_smart(dao, &cw_core::msg::QueryMsg::VotingModule {})?;
                let token_addr: Addr = deps.querier.query_wasm_smart(
                    voting_module,
                    &cw_core_interface::voting::Query::TokenContract {},
                )?;
                token_addr
            }
        };
        // Make an info query as a smoke test that we are indeed
        // working with a token here. We can't turbofish this
        // type. See <https://github.com/rust-lang/rust/issues/83701>.
        //
        // This also covers the case where a misbehaving core contract
        // has returned an invalid address from the `TokenContract`
        // query as this will fail if the address is bad.
        let _info: cw20::TokenInfoResponse = deps
            .querier
            .query_wasm_smart(token.clone(), &cw20::Cw20QueryMsg::TokenInfo {})?;
        Ok(CheckedDepositInfo {
            token,
            deposit,
            refund_failed_proposals,
        })
    }
}

pub fn get_deposit_msg(
    info: &Option<CheckedDepositInfo>,
    contract: &Addr,
    sender: &Addr,
) -> StdResult<Vec<CosmosMsg>> {
    match info {
        Some(info) => {
            if info.deposit.is_zero() {
                Ok(vec![])
            } else {
                let transfer_msg = WasmMsg::Execute {
                    contract_addr: info.token.to_string(),
                    funds: vec![],
                    msg: to_binary(&cw20::Cw20ExecuteMsg::TransferFrom {
                        owner: sender.to_string(),
                        recipient: contract.to_string(),
                        amount: info.deposit,
                    })?,
                };
                let transfer_msg: CosmosMsg = transfer_msg.into();
                Ok(vec![transfer_msg])
            }
        }
        None => Ok(vec![]),
    }
}

pub fn get_return_deposit_msg(
    deposit_info: &CheckedDepositInfo,
    proposer: &Addr,
) -> StdResult<Vec<CosmosMsg>> {
    if deposit_info.deposit.is_zero() {
        return Ok(vec![]);
    }
    let transfer_msg = WasmMsg::Execute {
        contract_addr: deposit_info.token.to_string(),
        funds: vec![],
        msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer {
            recipient: proposer.to_string(),
            amount: deposit_info.deposit,
        })?,
    };
    let transfer_msg: CosmosMsg = transfer_msg.into();
    Ok(vec![transfer_msg])
}